Commit 0efa75d6 by Sarina Canelake

i18n of common/lib/capa/capa files

Also pep8, pylint, and style fixes
Covers:
{capa_problem, correctmap, customerender,
inputtypes, responsetypes, util, xqueue_interface}.py
LMS-1744
parent 3584b246
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
# Each Response may have one or more Input entry fields. # Each Response may have one or more Input entry fields.
# The capa problem may include a solution. # The capa problem may include a solution.
# #
''' """
Main module which shows problems (of "capa" type). Main module which shows problems (of "capa" type).
This is used by capa_module. This is used by capa_module.
''' """
from datetime import datetime from datetime import datetime
import logging import logging
...@@ -108,9 +108,9 @@ class LoncapaSystem(object): ...@@ -108,9 +108,9 @@ class LoncapaSystem(object):
class LoncapaProblem(object): class LoncapaProblem(object):
''' """
Main class for capa Problems. Main class for capa Problems.
''' """
def __init__(self, problem_text, id, capa_system, state=None, seed=None): def __init__(self, problem_text, id, capa_system, state=None, seed=None):
""" """
...@@ -181,9 +181,9 @@ class LoncapaProblem(object): ...@@ -181,9 +181,9 @@ class LoncapaProblem(object):
self.extracted_tree = self._extract_html(self.tree) self.extracted_tree = self._extract_html(self.tree)
def do_reset(self): def do_reset(self):
''' """
Reset internal state to unfinished, with no answers Reset internal state to unfinished, with no answers
''' """
self.student_answers = dict() self.student_answers = dict()
self.correct_map = CorrectMap() self.correct_map = CorrectMap()
self.done = False self.done = False
...@@ -203,11 +203,11 @@ class LoncapaProblem(object): ...@@ -203,11 +203,11 @@ class LoncapaProblem(object):
return u"LoncapaProblem ({0})".format(self.problem_id) return u"LoncapaProblem ({0})".format(self.problem_id)
def get_state(self): def get_state(self):
''' """
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,
...@@ -216,9 +216,9 @@ class LoncapaProblem(object): ...@@ -216,9 +216,9 @@ class LoncapaProblem(object):
'done': self.done} 'done': self.done}
def get_max_score(self): def get_max_score(self):
''' """
Return the maximum score for this problem. Return the maximum score for this problem.
''' """
maxscore = 0 maxscore = 0
for responder in self.responders.values(): for responder in self.responders.values():
maxscore += responder.get_max_score() maxscore += responder.get_max_score()
...@@ -235,7 +235,7 @@ class LoncapaProblem(object): ...@@ -235,7 +235,7 @@ class LoncapaProblem(object):
try: try:
correct += self.correct_map.get_npoints(key) correct += self.correct_map.get_npoints(key)
except Exception: except Exception:
log.error('key=%s, correct_map = %s' % (key, self.correct_map)) log.error('key=%s, correct_map = %s', key, self.correct_map)
raise raise
if (not self.student_answers) or len(self.student_answers) == 0: if (not self.student_answers) or len(self.student_answers) == 0:
...@@ -246,12 +246,12 @@ class LoncapaProblem(object): ...@@ -246,12 +246,12 @@ class LoncapaProblem(object):
'total': self.get_max_score()} 'total': self.get_max_score()}
def update_score(self, score_msg, queuekey): def update_score(self, score_msg, queuekey):
''' """
Deliver grading response (e.g. from async code checking) to Deliver grading response (e.g. from async code checking) to
the specific ResponseType that requested grading the specific ResponseType that requested grading
Returns an updated CorrectMap Returns an updated CorrectMap
''' """
cmap = CorrectMap() cmap = CorrectMap()
cmap.update(self.correct_map) cmap.update(self.correct_map)
for responder in self.responders.values(): for responder in self.responders.values():
...@@ -263,12 +263,12 @@ class LoncapaProblem(object): ...@@ -263,12 +263,12 @@ class LoncapaProblem(object):
return cmap return cmap
def ungraded_response(self, xqueue_msg, queuekey): def ungraded_response(self, xqueue_msg, queuekey):
''' """
Handle any responses from the xqueue that do not contain grades Handle any responses from the xqueue that do not contain grades
Will try to pass the queue message to all inputtypes that can handle ungraded responses Will try to pass the queue message to all inputtypes that can handle ungraded responses
Does not return any value Does not return any value
''' """
# check against each inputtype # check against each inputtype
for the_input in self.inputs.values(): for the_input in self.inputs.values():
# if the input type has an ungraded function, pass in the values # if the input type has an ungraded function, pass in the values
...@@ -276,17 +276,17 @@ class LoncapaProblem(object): ...@@ -276,17 +276,17 @@ class LoncapaProblem(object):
the_input.ungraded_response(xqueue_msg, queuekey) the_input.ungraded_response(xqueue_msg, queuekey)
def is_queued(self): def is_queued(self):
''' """
Returns True if any part of the problem has been submitted to an external queue Returns True if any part of the problem has been submitted to an external queue
(e.g. for grading.) (e.g. for grading.)
''' """
return any(self.correct_map.is_queued(answer_id) for answer_id in self.correct_map) return any(self.correct_map.is_queued(answer_id) for answer_id in self.correct_map)
def get_recentmost_queuetime(self): def get_recentmost_queuetime(self):
''' """
Returns a DateTime object that represents the timestamp of the most recent Returns a DateTime object that represents the timestamp of the most recent
queueing request, or None if not queued queueing request, or None if not queued
''' """
if not self.is_queued(): if not self.is_queued():
return None return None
...@@ -304,7 +304,7 @@ class LoncapaProblem(object): ...@@ -304,7 +304,7 @@ class LoncapaProblem(object):
return max(queuetimes) return max(queuetimes)
def grade_answers(self, answers): def grade_answers(self, answers):
''' """
Grade student responses. Called by capa_module.check_problem. Grade student responses. Called by capa_module.check_problem.
`answers` is a dict of all the entries from request.POST, but with the first part `answers` is a dict of all the entries from request.POST, but with the first part
...@@ -313,7 +313,7 @@ class LoncapaProblem(object): ...@@ -313,7 +313,7 @@ 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
Calls the Response for each question in this problem, to do the actual grading. Calls the Response for each question in this problem, to do the actual grading.
''' """
# if answers include File objects, convert them to filenames. # if answers include File objects, convert them to filenames.
self.student_answers = convert_files_to_filenames(answers) self.student_answers = convert_files_to_filenames(answers)
...@@ -363,7 +363,6 @@ class LoncapaProblem(object): ...@@ -363,7 +363,6 @@ class LoncapaProblem(object):
# start new with empty CorrectMap # start new with empty CorrectMap
newcmap = CorrectMap() newcmap = CorrectMap()
# Call each responsetype instance to do actual grading # Call each responsetype instance to do actual grading
for responder in self.responders.values(): for responder in self.responders.values():
# File objects are passed only if responsetype explicitly allows # File objects are passed only if responsetype explicitly allows
...@@ -372,7 +371,8 @@ class LoncapaProblem(object): ...@@ -372,7 +371,8 @@ class LoncapaProblem(object):
# an earlier submission, so for now skip these entirely. # an earlier submission, so for now skip these entirely.
# TODO: figure out where to get file submissions when rescoring. # TODO: figure out where to get file submissions when rescoring.
if 'filesubmission' in responder.allowed_inputfields and student_answers is None: if 'filesubmission' in responder.allowed_inputfields and student_answers is None:
raise Exception("Cannot rescore problems with possible file submissions") _ = self.capa_system.i18n.ugettext
raise Exception(_("Cannot rescore problems with possible file submissions"))
# use 'student_answers' only if it is provided, and if it might contain a file # use 'student_answers' only if it is provided, and if it might contain a file
# submission that would not exist in the persisted "student_answers". # submission that would not exist in the persisted "student_answers".
...@@ -404,7 +404,7 @@ class LoncapaProblem(object): ...@@ -404,7 +404,7 @@ class LoncapaProblem(object):
if answer: if answer:
answer_map[entry.get('id')] = contextualize_text(answer, self.context) answer_map[entry.get('id')] = contextualize_text(answer, self.context)
log.debug('answer_map = %s' % answer_map) log.debug('answer_map = %s', answer_map)
return answer_map return answer_map
def get_answer_ids(self): def get_answer_ids(self):
...@@ -420,18 +420,18 @@ class LoncapaProblem(object): ...@@ -420,18 +420,18 @@ class LoncapaProblem(object):
return answer_ids return answer_ids
def get_html(self): def get_html(self):
''' """
Main method called externally to get the HTML to be rendered for this capa Problem. Main method called externally to get the HTML to be rendered for this capa Problem.
''' """
html = contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context) html = contextualize_text(etree.tostring(self._extract_html(self.tree)), self.context)
return html return html
def handle_input_ajax(self, data): def handle_input_ajax(self, data):
''' """
InputTypes can support specialized AJAX calls. Find the correct input and pass along the correct data InputTypes can support specialized AJAX calls. Find the correct input and pass along the correct data
Also, parse out the dispatch from the get so that it can be passed onto the input type nicely Also, parse out the dispatch from the get so that it can be passed onto the input type nicely
''' """
# pull out the id # pull out the id
input_id = data['input_id'] input_id = data['input_id']
...@@ -439,16 +439,16 @@ class LoncapaProblem(object): ...@@ -439,16 +439,16 @@ class LoncapaProblem(object):
dispatch = data['dispatch'] dispatch = data['dispatch']
return self.inputs[input_id].handle_ajax(dispatch, data) return self.inputs[input_id].handle_ajax(dispatch, data)
else: else:
log.warning("Could not find matching input for id: %s" % input_id) log.warning("Could not find matching input for id: %s", input_id)
return {} return {}
# ======= Private Methods Below ======== # ======= Private Methods Below ========
def _process_includes(self): def _process_includes(self):
''' """
Handle any <include file="foo"> tags by reading in the specified file and inserting it Handle any <include file="foo"> tags by reading in the specified file and inserting it
into our XML tree. Fail gracefully if debugging. into our XML tree. Fail gracefully if debugging.
''' """
includes = self.tree.findall('.//include') includes = self.tree.findall('.//include')
for inc in includes: for inc in includes:
filename = inc.get('file') filename = inc.get('file')
...@@ -458,14 +458,12 @@ class LoncapaProblem(object): ...@@ -458,14 +458,12 @@ class LoncapaProblem(object):
ifp = self.capa_system.filestore.open(filename) ifp = self.capa_system.filestore.open(filename)
except Exception as err: except Exception as err:
log.warning( log.warning(
'Error %s in problem xml include: %s' % ( 'Error %s in problem xml include: %s',
err, etree.tostring(inc, pretty_print=True) err,
) etree.tostring(inc, pretty_print=True)
) )
log.warning( log.warning(
'Cannot find file %s in %s' % ( 'Cannot find file %s in %s', filename, self.capa_system.filestore
filename, self.capa_system.filestore
)
) )
# if debugging, don't fail - just log error # if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users # TODO (vshnayder): need real error handling, display to users
...@@ -478,11 +476,11 @@ class LoncapaProblem(object): ...@@ -478,11 +476,11 @@ class LoncapaProblem(object):
incxml = etree.XML(ifp.read()) incxml = etree.XML(ifp.read())
except Exception as err: except Exception as err:
log.warning( log.warning(
'Error %s in problem xml include: %s' % ( 'Error %s in problem xml include: %s',
err, etree.tostring(inc, pretty_print=True) err,
) etree.tostring(inc, pretty_print=True)
) )
log.warning('Cannot parse XML in %s' % (filename)) log.warning('Cannot parse XML in %s', (filename))
# if debugging, don't fail - just log error # if debugging, don't fail - just log error
# TODO (vshnayder): same as above # TODO (vshnayder): same as above
if not self.capa_system.DEBUG: if not self.capa_system.DEBUG:
...@@ -522,7 +520,7 @@ class LoncapaProblem(object): ...@@ -522,7 +520,7 @@ class LoncapaProblem(object):
# Check that we are within the filestore tree. # Check that we are within the filestore tree.
reldir = os.path.relpath(dir, self.capa_system.filestore.root_path) reldir = os.path.relpath(dir, self.capa_system.filestore.root_path)
if ".." in reldir: if ".." in reldir:
log.warning("Ignoring Python directory outside of course: %r" % dir) log.warning("Ignoring Python directory outside of course: %r", dir)
continue continue
abs_dir = os.path.normpath(dir) abs_dir = os.path.normpath(dir)
...@@ -531,13 +529,13 @@ class LoncapaProblem(object): ...@@ -531,13 +529,13 @@ class LoncapaProblem(object):
return path return path
def _extract_context(self, tree): def _extract_context(self, tree):
''' """
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
variables for problem answer checking. variables for problem answer checking.
Problem XML goes to Python execution context. Runs everything in script tags. Problem XML goes to Python execution context. Runs everything in script tags.
''' """
context = {} context = {}
context['seed'] = self.seed context['seed'] = self.seed
all_code = '' all_code = ''
...@@ -584,7 +582,7 @@ class LoncapaProblem(object): ...@@ -584,7 +582,7 @@ class LoncapaProblem(object):
return context return context
def _extract_html(self, problemtree): # private def _extract_html(self, problemtree): # private
''' """
Main (private) function which converts Problem XML tree to HTML. Main (private) function which converts Problem XML tree to HTML.
Calls itself recursively. Calls itself recursively.
...@@ -592,7 +590,7 @@ class LoncapaProblem(object): ...@@ -592,7 +590,7 @@ class LoncapaProblem(object):
Calls render_html of Response instances to render responses into XHTML. Calls render_html of Response instances to render responses into XHTML.
Used by get_html. Used by get_html.
''' """
if not isinstance(problemtree.tag, basestring): if not isinstance(problemtree.tag, basestring):
# Comment and ProcessingInstruction nodes are not Elements, # Comment and ProcessingInstruction nodes are not Elements,
# and we're ok leaving those behind. # and we're ok leaving those behind.
...@@ -632,13 +630,17 @@ class LoncapaProblem(object): ...@@ -632,13 +630,17 @@ class LoncapaProblem(object):
self.input_state[input_id] = {} self.input_state[input_id] = {}
# do the rendering # do the rendering
state = {'value': value, state = {
'value': value,
'status': status, 'status': status,
'id': input_id, 'id': input_id,
'input_state': self.input_state[input_id], 'input_state': self.input_state[input_id],
'feedback': {'message': msg, 'feedback': {
'message': msg,
'hint': hint, 'hint': hint,
'hintmode': hintmode, }} 'hintmode': hintmode,
}
}
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag) input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
# save the input type so that we can make ajax calls on it if we need to # save the input type so that we can make ajax calls on it if we need to
...@@ -678,7 +680,7 @@ class LoncapaProblem(object): ...@@ -678,7 +680,7 @@ class LoncapaProblem(object):
return tree return tree
def _preprocess_problem(self, tree): # private def _preprocess_problem(self, tree): # private
''' """
Assign IDs to all the responses Assign IDs to all the responses
Assign sub-IDs to all entries (textline, schematic, etc.) Assign sub-IDs to all entries (textline, schematic, etc.)
Annoted correctness and value Annoted correctness and value
...@@ -687,7 +689,7 @@ class LoncapaProblem(object): ...@@ -687,7 +689,7 @@ class LoncapaProblem(object):
Also create capa Response instances for each responsetype and save as self.responders Also create capa Response instances for each responsetype and save as self.responders
Obtain all responder answers and save as self.responder_answers dict (key = response) Obtain all responder answers and save as self.responder_answers dict (key = response)
''' """
response_id = 1 response_id = 1
self.responders = {} self.responders = {}
for response in tree.xpath('//' + "|//".join(responsetypes.registry.registered_tags())): for response in tree.xpath('//' + "|//".join(responsetypes.registry.registered_tags())):
......
...@@ -63,13 +63,13 @@ class CorrectMap(object): ...@@ -63,13 +63,13 @@ class CorrectMap(object):
return repr(self.cmap) return repr(self.cmap)
def get_dict(self): def get_dict(self):
''' """
return dict version of self return dict version of self
''' """
return self.cmap return self.cmap
def set_dict(self, correct_map): def set_dict(self, correct_map):
''' """
Set internal dict of CorrectMap to provided correct_map dict Set internal dict of CorrectMap to provided correct_map dict
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This
...@@ -85,7 +85,7 @@ class CorrectMap(object): ...@@ -85,7 +85,7 @@ class CorrectMap(object):
Special migration case: Special migration case:
If correct_map is a one-level dict, then convert it to the new dict of dicts format. If correct_map is a one-level dict, then convert it to the new dict of dicts format.
''' """
# empty current dict # empty current dict
self.__init__() self.__init__()
...@@ -149,17 +149,17 @@ class CorrectMap(object): ...@@ -149,17 +149,17 @@ class CorrectMap(object):
return self.get_property(answer_id, 'hintmode', None) return self.get_property(answer_id, 'hintmode', None)
def set_hint_and_mode(self, answer_id, hint, hintmode): def set_hint_and_mode(self, answer_id, hint, hintmode):
''' """
- hint : (string) HTML text for hint - hint : (string) HTML text for hint
- hintmode : (string) mode for hint display ('always' or 'on_request') - hintmode : (string) mode for hint display ('always' or 'on_request')
''' """
self.set_property(answer_id, 'hint', hint) self.set_property(answer_id, 'hint', hint)
self.set_property(answer_id, 'hintmode', hintmode) self.set_property(answer_id, 'hintmode', hintmode)
def update(self, other_cmap): def update(self, other_cmap):
''' """
Update this CorrectMap with the contents of another CorrectMap Update this CorrectMap with the contents of another CorrectMap
''' """
if not isinstance(other_cmap, CorrectMap): if not isinstance(other_cmap, CorrectMap):
raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap) raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap)
self.cmap.update(other_cmap.get_dict()) self.cmap.update(other_cmap.get_dict())
......
...@@ -26,7 +26,7 @@ class MathRenderer(object): ...@@ -26,7 +26,7 @@ class MathRenderer(object):
tags = ['math'] tags = ['math']
def __init__(self, system, xml): def __init__(self, system, xml):
r''' r"""
Render math using latex-like formatting. Render math using latex-like formatting.
Examples: Examples:
...@@ -37,7 +37,7 @@ class MathRenderer(object): ...@@ -37,7 +37,7 @@ class MathRenderer(object):
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline] We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
TODO: use shorter tags (but this will require converting problem XML files!) TODO: use shorter tags (but this will require converting problem XML files!)
''' """
self.system = system self.system = system
self.xml = xml self.xml = xml
...@@ -79,13 +79,13 @@ registry.register(MathRenderer) ...@@ -79,13 +79,13 @@ registry.register(MathRenderer)
class SolutionRenderer(object): class SolutionRenderer(object):
''' """
A solution is just a <span>...</span> which is given an ID, that is used for displaying an A solution is just a <span>...</span> which is given an ID, that is used for displaying an
extended answer (a problem "solution") after "show answers" is pressed. extended answer (a problem "solution") after "show answers" is pressed.
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
ajax call. ajax call.
''' """
tags = ['solution'] tags = ['solution']
def __init__(self, system, xml): def __init__(self, system, xml):
......
...@@ -102,7 +102,8 @@ class Attribute(object): ...@@ -102,7 +102,8 @@ class Attribute(object):
val = element.get(self.name) val = element.get(self.name)
if self.default == self._sentinel and val is None: if self.default == self._sentinel and val is None:
raise ValueError( raise ValueError(
'Missing required attribute {0}.'.format(self.name)) 'Missing required attribute {0}.'.format(self.name)
)
if val is None: if val is None:
# not required, so return default # not required, so return default
...@@ -156,8 +157,9 @@ class InputTypeBase(object): ...@@ -156,8 +157,9 @@ class InputTypeBase(object):
self.input_id = state.get('id', xml.get('id')) self.input_id = state.get('id', xml.get('id'))
if self.input_id is None: if self.input_id is None:
raise ValueError("input id state is None. xml is {0}".format( raise ValueError(
etree.tostring(xml))) "input id state is None. xml is {0}".format(etree.tostring(xml))
)
self.value = state.get('value', '') self.value = state.get('value', '')
...@@ -259,8 +261,9 @@ class InputTypeBase(object): ...@@ -259,8 +261,9 @@ class InputTypeBase(object):
'msg': self.msg, 'msg': self.msg,
'STATIC_URL': self.capa_system.STATIC_URL, 'STATIC_URL': self.capa_system.STATIC_URL,
} }
context.update((a, v) for ( context.update(
a, v) in self.loaded_attributes.iteritems() if a in self.to_render) (a, v) for (a, v) in self.loaded_attributes.iteritems() if a in self.to_render
)
context.update(self._extra_context()) context.update(self._extra_context())
return context return context
...@@ -394,7 +397,7 @@ class ChoiceGroup(InputTypeBase): ...@@ -394,7 +397,7 @@ class ChoiceGroup(InputTypeBase):
@staticmethod @staticmethod
def extract_choices(element): def extract_choices(element):
''' """
Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and
CheckboxGroup. CheckboxGroup.
...@@ -402,7 +405,7 @@ class ChoiceGroup(InputTypeBase): ...@@ -402,7 +405,7 @@ class ChoiceGroup(InputTypeBase):
TODO: allow order of choices to be randomized, following lon-capa spec. Use TODO: allow order of choices to be randomized, following lon-capa spec. Use
"location" attribute, ie random, top, bottom. "location" attribute, ie random, top, bottom.
''' """
choices = [] choices = []
...@@ -574,7 +577,8 @@ class TextLine(InputTypeBase): ...@@ -574,7 +577,8 @@ class TextLine(InputTypeBase):
# Preprocessor to insert between raw input and Mathjax # Preprocessor to insert between raw input and Mathjax
self.preprocessor = { self.preprocessor = {
'class_name': self.loaded_attributes['preprocessorClassName'], 'class_name': self.loaded_attributes['preprocessorClassName'],
'script_src': self.loaded_attributes['preprocessorSrc']} 'script_src': self.loaded_attributes['preprocessorSrc'],
}
if None in self.preprocessor.values(): if None in self.preprocessor.values():
self.preprocessor = None self.preprocessor = None
...@@ -594,9 +598,6 @@ class FileSubmission(InputTypeBase): ...@@ -594,9 +598,6 @@ class FileSubmission(InputTypeBase):
template = "filesubmission.html" template = "filesubmission.html"
tags = ['filesubmission'] tags = ['filesubmission']
# pulled out for testing
submitted_msg = ("Your file(s) have been submitted; as soon as your submission is"
" graded, this message will be replaced with the grader's feedback.")
@staticmethod @staticmethod
def parse_files(files): def parse_files(files):
...@@ -618,6 +619,11 @@ class FileSubmission(InputTypeBase): ...@@ -618,6 +619,11 @@ class FileSubmission(InputTypeBase):
Do some magic to handle queueing status (render as "queued" instead of "incomplete"), Do some magic to handle queueing status (render as "queued" instead of "incomplete"),
pull queue_len from the msg field. (TODO: get rid of the queue_len hack). pull queue_len from the msg field. (TODO: get rid of the queue_len hack).
""" """
_ = self.capa_system.i18n.ugettext
submitted_msg = _("Your file(s) have been submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback.")
self.submitted_msg = submitted_msg
# Check if problem has been queued # Check if problem has been queued
self.queue_len = 0 self.queue_len = 0
# Flag indicating that the problem has been queued, 'msg' is length of # Flag indicating that the problem has been queued, 'msg' is length of
...@@ -625,7 +631,7 @@ class FileSubmission(InputTypeBase): ...@@ -625,7 +631,7 @@ class FileSubmission(InputTypeBase):
if self.status == 'incomplete': if self.status == 'incomplete':
self.status = 'queued' self.status = 'queued'
self.queue_len = self.msg self.queue_len = self.msg
self.msg = FileSubmission.submitted_msg self.msg = self.submitted_msg
def _extra_context(self): def _extra_context(self):
return {'queue_len': self.queue_len, } return {'queue_len': self.queue_len, }
...@@ -641,15 +647,13 @@ class CodeInput(InputTypeBase): ...@@ -641,15 +647,13 @@ class CodeInput(InputTypeBase):
""" """
template = "codeinput.html" template = "codeinput.html"
tags = ['codeinput', tags = [
'codeinput',
'textbox', 'textbox',
# Another (older) name--at some point we may want to make it use a # Another (older) name--at some point we may want to make it use a
# non-codemirror editor. # non-codemirror editor.
] ]
# pulled out for testing
submitted_msg = ("Submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback.")
@classmethod @classmethod
def get_attributes(cls): def get_attributes(cls):
...@@ -686,7 +690,12 @@ class CodeInput(InputTypeBase): ...@@ -686,7 +690,12 @@ class CodeInput(InputTypeBase):
self.msg = self.submitted_msg self.msg = self.submitted_msg
def setup(self): def setup(self):
''' setup this input type ''' """ setup this input type """
_ = self.capa_system.i18n.ugettext
submitted_msg = _("Your answer has been submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback.")
self.submitted_msg = submitted_msg
self.setup_code_response_rendering() self.setup_code_response_rendering()
def _extra_context(self): def _extra_context(self):
...@@ -699,7 +708,7 @@ class CodeInput(InputTypeBase): ...@@ -699,7 +708,7 @@ class CodeInput(InputTypeBase):
@registry.register @registry.register
class MatlabInput(CodeInput): class MatlabInput(CodeInput):
''' """
InputType for handling Matlab code input InputType for handling Matlab code input
TODO: API_KEY will go away once we have a way to specify it per-course TODO: API_KEY will go away once we have a way to specify it per-course
...@@ -710,17 +719,20 @@ class MatlabInput(CodeInput): ...@@ -710,17 +719,20 @@ class MatlabInput(CodeInput):
%api_key=API_KEY %api_key=API_KEY
</plot_payload> </plot_payload>
</matlabinput> </matlabinput>
''' """
template = "matlabinput.html" template = "matlabinput.html"
tags = ['matlabinput'] tags = ['matlabinput']
plot_submitted_msg = ("Submitted. As soon as a response is returned, "
"this message will be replaced by that feedback.")
def setup(self): def setup(self):
''' """
Handle matlab-specific parsing Handle matlab-specific parsing
''' """
_ = self.capa_system.i18n.ugettext
submitted_msg = _("Submitted. As soon as a response is returned, "
"this message will be replaced by that feedback.")
self.submitted_msg = submitted_msg
self.setup_code_response_rendering() self.setup_code_response_rendering()
xml = self.xml xml = self.xml
...@@ -736,10 +748,10 @@ class MatlabInput(CodeInput): ...@@ -736,10 +748,10 @@ class MatlabInput(CodeInput):
if 'queuestate' in self.input_state and self.input_state['queuestate'] == 'queued': if 'queuestate' in self.input_state and self.input_state['queuestate'] == 'queued':
self.status = 'queued' self.status = 'queued'
self.queue_len = 1 self.queue_len = 1
self.msg = self.plot_submitted_msg self.msg = self.submitted_msg
def handle_ajax(self, dispatch, data): def handle_ajax(self, dispatch, data):
''' """
Handle AJAX calls directed to this input Handle AJAX calls directed to this input
Args: Args:
...@@ -748,14 +760,14 @@ class MatlabInput(CodeInput): ...@@ -748,14 +760,14 @@ class MatlabInput(CodeInput):
Returns: Returns:
dict - 'success' - whether or not we successfully queued this submission dict - 'success' - whether or not we successfully queued this submission
- 'message' - message to be rendered in case of error - 'message' - message to be rendered in case of error
''' """
if dispatch == 'plot': if dispatch == 'plot':
return self._plot_data(data) return self._plot_data(data)
return {} return {}
def ungraded_response(self, queue_msg, queuekey): def ungraded_response(self, queue_msg, queuekey):
''' """
Handle the response from the XQueue Handle the response from the XQueue
Stores the response in the input_state so it can be rendered later Stores the response in the input_state so it can be rendered later
...@@ -765,7 +777,7 @@ class MatlabInput(CodeInput): ...@@ -765,7 +777,7 @@ class MatlabInput(CodeInput):
Returns: Returns:
nothing nothing
''' """
# check the queuekey against the saved queuekey # check the queuekey against the saved queuekey
if('queuestate' in self.input_state and self.input_state['queuestate'] == 'queued' if('queuestate' in self.input_state and self.input_state['queuestate'] == 'queued'
and self.input_state['queuekey'] == queuekey): and self.input_state['queuekey'] == queuekey):
...@@ -787,7 +799,7 @@ class MatlabInput(CodeInput): ...@@ -787,7 +799,7 @@ class MatlabInput(CodeInput):
return True return True
def _extra_context(self): def _extra_context(self):
''' Set up additional context variables''' """ Set up additional context variables"""
extra_context = { extra_context = {
'queue_len': str(self.queue_len), 'queue_len': str(self.queue_len),
'queue_msg': self.queue_msg, 'queue_msg': self.queue_msg,
...@@ -796,31 +808,31 @@ class MatlabInput(CodeInput): ...@@ -796,31 +808,31 @@ class MatlabInput(CodeInput):
return extra_context return extra_context
def _parse_data(self, queue_msg): def _parse_data(self, queue_msg):
''' """
Parses the message out of the queue message Parses the message out of the queue message
Args: Args:
queue_msg (str) - a JSON encoded string queue_msg (str) - a JSON encoded string
Returns: Returns:
returns the value for the the key 'msg' in queue_msg returns the value for the the key 'msg' in queue_msg
''' """
try: try:
result = json.loads(queue_msg) result = json.loads(queue_msg)
except (TypeError, ValueError): except (TypeError, ValueError):
log.error("External message should be a JSON serialized dict." log.error("External message should be a JSON serialized dict."
" Received queue_msg = %s" % queue_msg) " Received queue_msg = %s", queue_msg)
raise raise
msg = result['msg'] msg = result['msg']
return msg return msg
def _plot_data(self, data): def _plot_data(self, data):
''' """
AJAX handler for the plot button AJAX handler for the plot button
Args: Args:
get (dict) - should have key 'submission' which contains the student submission get (dict) - should have key 'submission' which contains the student submission
Returns: Returns:
dict - 'success' - whether or not we successfully queued this submission dict - 'success' - whether or not we successfully queued this submission
- 'message' - message to be rendered in case of error - 'message' - message to be rendered in case of error
''' """
# only send data if xqueue exists # only send data if xqueue exists
if self.capa_system.xqueue is None: if self.capa_system.xqueue is None:
return {'success': False, 'message': 'Cannot connect to the queue'} return {'success': False, 'message': 'Cannot connect to the queue'}
...@@ -843,11 +855,15 @@ class MatlabInput(CodeInput): ...@@ -843,11 +855,15 @@ class MatlabInput(CodeInput):
queue_name=self.queuename) queue_name=self.queuename)
# construct xqueue body # construct xqueue body
student_info = {'anonymous_student_id': anonymous_student_id, student_info = {
'submission_time': qtime} 'anonymous_student_id': anonymous_student_id,
contents = {'grader_payload': self.plot_payload, 'submission_time': qtime
}
contents = {
'grader_payload': self.plot_payload,
'student_info': json.dumps(student_info), 'student_info': json.dumps(student_info),
'student_response': response} 'student_response': response
}
(error, msg) = qinterface.send_to_queue(header=xheader, (error, msg) = qinterface.send_to_queue(header=xheader,
body=json.dumps(contents)) body=json.dumps(contents))
...@@ -881,7 +897,8 @@ class Schematic(InputTypeBase): ...@@ -881,7 +897,8 @@ class Schematic(InputTypeBase):
Attribute('parts', None), Attribute('parts', None),
Attribute('analyses', None), Attribute('analyses', None),
Attribute('initial_value', None), Attribute('initial_value', None),
Attribute('submit_analyses', None), ] Attribute('submit_analyses', None),
]
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
...@@ -1011,10 +1028,10 @@ class ChemicalEquationInput(InputTypeBase): ...@@ -1011,10 +1028,10 @@ class ChemicalEquationInput(InputTypeBase):
} }
def handle_ajax(self, dispatch, data): def handle_ajax(self, dispatch, data):
''' """
Since we only have chemcalc preview this input, check to see if it Since we only have chemcalc preview this input, check to see if it
matches the corresponding dispatch and send it through if it does matches the corresponding dispatch and send it through if it does
''' """
if dispatch == 'preview_chemcalc': if dispatch == 'preview_chemcalc':
return self.preview_chemcalc(data) return self.preview_chemcalc(data)
return {} return {}
...@@ -1097,10 +1114,10 @@ class FormulaEquationInput(InputTypeBase): ...@@ -1097,10 +1114,10 @@ class FormulaEquationInput(InputTypeBase):
} }
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
''' """
Since we only have formcalc preview this input, check to see if it Since we only have formcalc preview this input, check to see if it
matches the corresponding dispatch and send it through if it does matches the corresponding dispatch and send it through if it does
''' """
if dispatch == 'preview_formcalc': if dispatch == 'preview_formcalc':
return self.preview_formcalc(get) return self.preview_formcalc(get)
return {} return {}
...@@ -1407,7 +1424,7 @@ class AnnotationInput(InputTypeBase): ...@@ -1407,7 +1424,7 @@ class AnnotationInput(InputTypeBase):
self._validate_options() self._validate_options()
def _find_options(self): def _find_options(self):
''' Returns an array of dicts where each dict represents an option. ''' """ Returns an array of dicts where each dict represents an option. """
elements = self.xml.findall('./options/option') elements = self.xml.findall('./options/option')
return [{ return [{
'id': index, 'id': index,
...@@ -1416,7 +1433,7 @@ class AnnotationInput(InputTypeBase): ...@@ -1416,7 +1433,7 @@ class AnnotationInput(InputTypeBase):
} for (index, option) in enumerate(elements)] } for (index, option) in enumerate(elements)]
def _validate_options(self): def _validate_options(self):
''' Raises a ValueError if the choice attribute is missing or invalid. ''' """ Raises a ValueError if the choice attribute is missing or invalid. """
valid_choices = ('correct', 'partially-correct', 'incorrect') valid_choices = ('correct', 'partially-correct', 'incorrect')
for option in self.options: for option in self.options:
choice = option['choice'] choice = option['choice']
...@@ -1427,7 +1444,7 @@ class AnnotationInput(InputTypeBase): ...@@ -1427,7 +1444,7 @@ class AnnotationInput(InputTypeBase):
choice, ', '.join(valid_choices))) choice, ', '.join(valid_choices)))
def _unpack(self, json_value): def _unpack(self, json_value):
''' Unpacks the json input state into a dict. ''' """ Unpacks the json input state into a dict. """
d = json.loads(json_value) d = json.loads(json_value)
if type(d) != dict: if type(d) != dict:
d = {} d = {}
......
# #
# File: courseware/capa/responsetypes.py # File: courseware/capa/responsetypes.py
# #
''' """
Problem response evaluation. Handles checking of student responses, Problem response evaluation. Handles checking of student responses,
of a variety of types. of a variety of types.
Used by capa_problem.py Used by capa_problem.py
''' """
# standard library imports # standard library imports
import abc import abc
...@@ -57,25 +57,25 @@ CORRECTMAP_PY = None ...@@ -57,25 +57,25 @@ CORRECTMAP_PY = None
class LoncapaProblemError(Exception): class LoncapaProblemError(Exception):
''' """
Error in specification of a problem Error in specification of a problem
''' """
pass pass
class ResponseError(Exception): class ResponseError(Exception):
''' """
Error for failure in processing a response, including Error for failure in processing a response, including
exceptions that occur when executing a custom script. exceptions that occur when executing a custom script.
''' """
pass pass
class StudentInputError(Exception): class StudentInputError(Exception):
''' """
Error for an invalid student input. Error for an invalid student input.
For example, submitting a string when the problem expects a number For example, submitting a string when the problem expects a number
''' """
pass pass
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
...@@ -130,7 +130,7 @@ class LoncapaResponse(object): ...@@ -130,7 +130,7 @@ class LoncapaResponse(object):
required_attributes = [] required_attributes = []
def __init__(self, xml, inputfields, context, system): def __init__(self, xml, inputfields, context, system):
''' """
Init is passed the following arguments: Init is passed the following arguments:
- xml : ElementTree of this Response - xml : ElementTree of this Response
...@@ -138,7 +138,7 @@ class LoncapaResponse(object): ...@@ -138,7 +138,7 @@ class LoncapaResponse(object):
- context : script processor context - context : script processor context
- system : LoncapaSystem instance which provides OS, rendering, and user context - system : LoncapaSystem instance which provides OS, rendering, and user context
''' """
self.xml = xml self.xml = xml
self.inputfields = inputfields self.inputfields = inputfields
self.context = context self.context = context
...@@ -146,6 +146,8 @@ class LoncapaResponse(object): ...@@ -146,6 +146,8 @@ class LoncapaResponse(object):
self.id = xml.get('id') self.id = xml.get('id')
# The LoncapaProblemError messages here do not need to be translated as they are
# only displayed to the user when settings.DEBUG is True
for abox in inputfields: for abox in inputfields:
if abox.tag not in self.allowed_inputfields: if abox.tag not in self.allowed_inputfields:
msg = "%s: cannot have input field %s" % ( msg = "%s: cannot have input field %s" % (
...@@ -194,20 +196,20 @@ class LoncapaResponse(object): ...@@ -194,20 +196,20 @@ class LoncapaResponse(object):
self.setup_response() self.setup_response()
def get_max_score(self): def get_max_score(self):
''' """
Return the total maximum points of all answer fields under this Response Return the total maximum points of all answer fields under this Response
''' """
return sum(self.maxpoints.values()) return sum(self.maxpoints.values())
def render_html(self, renderer, response_msg=''): def render_html(self, renderer, response_msg=''):
''' """
Return XHTML Element tree representation of this Response. Return XHTML Element tree representation of this Response.
Arguments: Arguments:
- renderer : procedure which produces HTML given an ElementTree - renderer : procedure which produces HTML given an ElementTree
- response_msg: a message displayed at the end of the Response - response_msg: a message displayed at the end of the Response
''' """
# render ourself as a <span> + our content # render ourself as a <span> + our content
tree = etree.Element('span') tree = etree.Element('span')
...@@ -229,12 +231,12 @@ class LoncapaResponse(object): ...@@ -229,12 +231,12 @@ class LoncapaResponse(object):
return tree return tree
def evaluate_answers(self, student_answers, old_cmap): def evaluate_answers(self, student_answers, old_cmap):
''' """
Called by capa_problem.LoncapaProblem to evaluate student answers, and to Called by capa_problem.LoncapaProblem to evaluate student answers, and to
generate hints (if any). generate hints (if any).
Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id. Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id.
''' """
new_cmap = self.get_score(student_answers) new_cmap = self.get_score(student_answers)
self.get_hints(convert_files_to_filenames( self.get_hints(convert_files_to_filenames(
student_answers), new_cmap, old_cmap) student_answers), new_cmap, old_cmap)
...@@ -242,14 +244,14 @@ class LoncapaResponse(object): ...@@ -242,14 +244,14 @@ class LoncapaResponse(object):
return new_cmap return new_cmap
def get_hints(self, student_answers, new_cmap, old_cmap): def get_hints(self, student_answers, new_cmap, old_cmap):
''' """
Generate adaptive hints for this problem based on student answers, the old CorrectMap, Generate adaptive hints for this problem based on student answers, the old CorrectMap,
and the new CorrectMap produced by get_score. and the new CorrectMap produced by get_score.
Does not return anything. Does not return anything.
Modifies new_cmap, by adding hints to answer_id entries as appropriate. Modifies new_cmap, by adding hints to answer_id entries as appropriate.
''' """
hintgroup = self.xml.find('hintgroup') hintgroup = self.xml.find('hintgroup')
if hintgroup is None: if hintgroup is None:
return return
...@@ -301,9 +303,10 @@ class LoncapaResponse(object): ...@@ -301,9 +303,10 @@ class LoncapaResponse(object):
unsafely=self.capa_system.can_execute_unsafe_code(), unsafely=self.capa_system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
msg = 'Error %s in evaluating hint function %s' % (err, hintfn) _ = self.capa_system.i18n.ugettext
msg += "\nSee XML source line %s" % getattr( msg = _('Error {err} in evaluating hint function {hintfn}.').format(err=err, hintfn=hintfn)
self.xml, 'sourceline', '<unavailable>') sourcenum = getattr(self.xml, 'sourceline', _('(Source code line unavailable)'))
msg += "\n" + _("See XML source line {sourcenum}.").format(sourcenum=sourcenum)
raise ResponseError(msg) raise ResponseError(msg)
new_cmap.set_dict(globals_dict['new_cmap_dict']) new_cmap.set_dict(globals_dict['new_cmap_dict'])
...@@ -346,24 +349,24 @@ class LoncapaResponse(object): ...@@ -346,24 +349,24 @@ class LoncapaResponse(object):
@abc.abstractmethod @abc.abstractmethod
def get_score(self, student_answers): def get_score(self, student_answers):
''' """
Return a CorrectMap for the answers expected vs given. This includes Return a CorrectMap for the answers expected vs given. This includes
(correctness, npoints, msg) for each answer_id. (correctness, npoints, msg) for each answer_id.
Arguments: Arguments:
- student_answers : dict of (answer_id, answer) where answer = student input (string) - student_answers : dict of (answer_id, answer) where answer = student input (string)
''' """
pass pass
@abc.abstractmethod @abc.abstractmethod
def get_answers(self): def get_answers(self):
''' """
Return a dict of (answer_id, answer_text) for each answer for this question. Return a dict of (answer_id, answer_text) for each answer for this question.
''' """
pass pass
def check_hint_condition(self, hxml_set, student_answers): def check_hint_condition(self, hxml_set, student_answers):
''' """
Return a list of hints to show. Return a list of hints to show.
- hxml_set : list of Element trees, each specifying a condition to be - hxml_set : list of Element trees, each specifying a condition to be
...@@ -373,7 +376,7 @@ class LoncapaResponse(object): ...@@ -373,7 +376,7 @@ class LoncapaResponse(object):
Returns a list of names of hint conditions which were satisfied. Those are used Returns a list of names of hint conditions which were satisfied. Those are used
to determine which hints are displayed. to determine which hints are displayed.
''' """
pass pass
def setup_response(self): def setup_response(self):
...@@ -673,9 +676,9 @@ class ChoiceResponse(LoncapaResponse): ...@@ -673,9 +676,9 @@ class ChoiceResponse(LoncapaResponse):
'name') for choice in correct_xml]) 'name') for choice in correct_xml])
def assign_choice_names(self): def assign_choice_names(self):
''' """
Initialize name attributes in <choice> tags for this response. Initialize name attributes in <choice> tags for this response.
''' """
for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice', for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice',
id=self.xml.get('id'))): id=self.xml.get('id'))):
...@@ -729,12 +732,13 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -729,12 +732,13 @@ class MultipleChoiceResponse(LoncapaResponse):
self.correct_choices = [ self.correct_choices = [
contextualize_text(choice.get('name'), self.context) contextualize_text(choice.get('name'), self.context)
for choice in cxml for choice in cxml
if contextualize_text(choice.get('correct'), self.context) == "true"] if contextualize_text(choice.get('correct'), self.context) == "true"
]
def mc_setup_response(self): def mc_setup_response(self):
''' """
Initialize name attributes in <choice> stanzas in the <choicegroup> in this response. Initialize name attributes in <choice> stanzas in the <choicegroup> in this response.
''' """
i = 0 i = 0
for response in self.xml.xpath("choicegroup"): for response in self.xml.xpath("choicegroup"):
rtype = response.get('type') rtype = response.get('type')
...@@ -749,9 +753,9 @@ class MultipleChoiceResponse(LoncapaResponse): ...@@ -749,9 +753,9 @@ class MultipleChoiceResponse(LoncapaResponse):
choice.set("name", "choice_" + choice.get("name")) choice.set("name", "choice_" + choice.get("name"))
def get_score(self, student_answers): def get_score(self, student_answers):
''' """
grade student response. grade student response.
''' """
# log.debug('%s: student_answers=%s, correct_choices=%s' % ( # log.debug('%s: student_answers=%s, correct_choices=%s' % (
# unicode(self), student_answers, self.correct_choices)) # unicode(self), student_answers, self.correct_choices))
if (self.answer_id in student_answers if (self.answer_id in student_answers
...@@ -794,9 +798,9 @@ class TrueFalseResponse(MultipleChoiceResponse): ...@@ -794,9 +798,9 @@ class TrueFalseResponse(MultipleChoiceResponse):
@registry.register @registry.register
class OptionResponse(LoncapaResponse): class OptionResponse(LoncapaResponse):
''' """
TODO: handle direction and randomize TODO: handle direction and randomize
''' """
tags = ['optionresponse'] tags = ['optionresponse']
hint_tag = 'optionhint' hint_tag = 'optionhint'
...@@ -828,10 +832,10 @@ class OptionResponse(LoncapaResponse): ...@@ -828,10 +832,10 @@ class OptionResponse(LoncapaResponse):
@registry.register @registry.register
class NumericalResponse(LoncapaResponse): class NumericalResponse(LoncapaResponse):
''' """
This response type expects a number or formulaic expression that evaluates This response type expects a number or formulaic expression that evaluates
to a number (e.g. `4+5/2^2`), and accepts with a tolerance. to a number (e.g. `4+5/2^2`), and accepts with a tolerance.
''' """
tags = ['numericalresponse'] tags = ['numericalresponse']
hint_tag = 'numericalhint' hint_tag = 'numericalhint'
...@@ -877,19 +881,20 @@ class NumericalResponse(LoncapaResponse): ...@@ -877,19 +881,20 @@ class NumericalResponse(LoncapaResponse):
log.debug("Content error--answer '%s' is not a valid number", self.correct_answer) log.debug("Content error--answer '%s' is not a valid number", self.correct_answer)
_ = self.capa_system.i18n.ugettext _ = self.capa_system.i18n.ugettext
raise StudentInputError( raise StudentInputError(
_("There was a problem with the staff answer to this problem") _("There was a problem with the staff answer to this problem.")
) )
return correct_ans return correct_ans
def get_score(self, student_answers): def get_score(self, student_answers):
'''Grade a numeric response ''' """Grade a numeric response"""
student_answer = student_answers[self.answer_id] student_answer = student_answers[self.answer_id]
correct_float = self.get_staff_ans() correct_float = self.get_staff_ans()
_ = self.capa_system.i18n.ugettext
general_exception = StudentInputError( general_exception = StudentInputError(
u"Could not interpret '{0}' as a number".format(cgi.escape(student_answer)) _(u"Could not interpret '{student_answer}' as a number.").format(student_answer=cgi.escape(student_answer))
) )
# Begin `evaluator` block # Begin `evaluator` block
...@@ -898,7 +903,7 @@ class NumericalResponse(LoncapaResponse): ...@@ -898,7 +903,7 @@ class NumericalResponse(LoncapaResponse):
student_float = evaluator({}, {}, student_answer) student_float = evaluator({}, {}, student_answer)
except UndefinedVariable as undef_var: except UndefinedVariable as undef_var:
raise StudentInputError( raise StudentInputError(
u"You may not use variables ({0}) in numerical problems".format(undef_var.message) _(u"You may not use variables ({bad_variables}) in numerical problems.").format(bad_variables=undef_var.message)
) )
except ValueError as val_err: except ValueError as val_err:
if 'factorial' in val_err.message: if 'factorial' in val_err.message:
...@@ -907,14 +912,14 @@ class NumericalResponse(LoncapaResponse): ...@@ -907,14 +912,14 @@ class NumericalResponse(LoncapaResponse):
# ve.message will be: `factorial() only accepts integral values` or # ve.message will be: `factorial() only accepts integral values` or
# `factorial() not defined for negative values` # `factorial() not defined for negative values`
raise StudentInputError( raise StudentInputError(
("factorial function evaluated outside its domain:" _("factorial function evaluated outside its domain:"
"'{0}'").format(cgi.escape(student_answer)) "'{student_answer}'").format(student_answer=cgi.escape(student_answer))
) )
else: else:
raise general_exception raise general_exception
except ParseException: except ParseException:
raise StudentInputError( raise StudentInputError(
u"Invalid math syntax: '{0}'".format(cgi.escape(student_answer)) _(u"Invalid math syntax: '{student_answer}'").format(student_answer=cgi.escape(student_answer))
) )
except Exception: except Exception:
raise general_exception raise general_exception
...@@ -957,7 +962,7 @@ class NumericalResponse(LoncapaResponse): ...@@ -957,7 +962,7 @@ class NumericalResponse(LoncapaResponse):
@registry.register @registry.register
class StringResponse(LoncapaResponse): class StringResponse(LoncapaResponse):
''' """
This response type allows one or more answers. This response type allows one or more answers.
Additional answers are added by `additional_answer` tag. Additional answers are added by `additional_answer` tag.
...@@ -987,7 +992,7 @@ class StringResponse(LoncapaResponse): ...@@ -987,7 +992,7 @@ class StringResponse(LoncapaResponse):
</hintpart > </hintpart >
</hintgroup> </hintgroup>
</stringresponse> </stringresponse>
''' """
tags = ['stringresponse'] tags = ['stringresponse']
hint_tag = 'stringhint' hint_tag = 'stringhint'
allowed_inputfields = ['textline'] allowed_inputfields = ['textline']
...@@ -1020,7 +1025,7 @@ class StringResponse(LoncapaResponse): ...@@ -1020,7 +1025,7 @@ class StringResponse(LoncapaResponse):
self.xml.remove(el) self.xml.remove(el)
def get_score(self, student_answers): def get_score(self, student_answers):
'''Grade a string response ''' """Grade a string response """
student_answer = student_answers[self.answer_id].strip() student_answer = student_answers[self.answer_id].strip()
correct = self.check_string(self.correct_answer, student_answer) correct = self.check_string(self.correct_answer, student_answer)
return CorrectMap(self.answer_id, 'correct' if correct else 'incorrect') return CorrectMap(self.answer_id, 'correct' if correct else 'incorrect')
...@@ -1092,10 +1097,10 @@ class StringResponse(LoncapaResponse): ...@@ -1092,10 +1097,10 @@ class StringResponse(LoncapaResponse):
@registry.register @registry.register
class CustomResponse(LoncapaResponse): class CustomResponse(LoncapaResponse):
''' """
Custom response. The python code to be run should be in <answer>...</answer> Custom response. The python code to be run should be in <answer>...</answer>
or in a <script>...</script> or in a <script>...</script>
''' """
tags = ['customresponse'] tags = ['customresponse']
...@@ -1176,10 +1181,10 @@ class CustomResponse(LoncapaResponse): ...@@ -1176,10 +1181,10 @@ class CustomResponse(LoncapaResponse):
self.code = answer.text self.code = answer.text
def get_score(self, student_answers): def get_score(self, student_answers):
''' """
student_answers is a dict with everything from request.POST, but with the first part student_answers is a dict with everything from request.POST, but with the first part
of each key removed (the string before the first "_"). of each key removed (the string before the first "_").
''' """
log.debug('%s: student_answers=%s', unicode(self), student_answers) log.debug('%s: student_answers=%s', unicode(self), student_answers)
...@@ -1345,8 +1350,10 @@ class CustomResponse(LoncapaResponse): ...@@ -1345,8 +1350,10 @@ class CustomResponse(LoncapaResponse):
# Raise an exception # Raise an exception
else: else:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
_ = self.capa_system.i18n.ugettext
raise ResponseError( raise ResponseError(
"CustomResponse: check function returned an invalid dict") _("CustomResponse: check function returned an invalid dictionary!")
)
else: else:
correct = ['correct' if ret else 'incorrect'] * len(idset) correct = ['correct' if ret else 'incorrect'] * len(idset)
...@@ -1385,7 +1392,7 @@ class CustomResponse(LoncapaResponse): ...@@ -1385,7 +1392,7 @@ class CustomResponse(LoncapaResponse):
return "" return ""
def get_answers(self): def get_answers(self):
''' """
Give correct answer expected for this response. Give correct answer expected for this response.
use default_answer_map from entry elements (eg textline), use default_answer_map from entry elements (eg textline),
...@@ -1393,7 +1400,7 @@ class CustomResponse(LoncapaResponse): ...@@ -1393,7 +1400,7 @@ class CustomResponse(LoncapaResponse):
but for simplicity, if an "expect" attribute was given by the content author but for simplicity, if an "expect" attribute was given by the content author
ie <customresponse expect="foo" ...> then that. ie <customresponse expect="foo" ...> then that.
''' """
if len(self.answer_ids) > 1: if len(self.answer_ids) > 1:
return self.default_answer_map return self.default_answer_map
if self.expect: if self.expect:
...@@ -1401,12 +1408,12 @@ class CustomResponse(LoncapaResponse): ...@@ -1401,12 +1408,12 @@ class CustomResponse(LoncapaResponse):
return self.default_answer_map return self.default_answer_map
def _handle_exec_exception(self, err): def _handle_exec_exception(self, err):
''' """
Handle an exception raised during the execution of Handle an exception raised during the execution of
custom Python code. custom Python code.
Raises a ResponseError Raises a ResponseError
''' """
# Log the error if we are debugging # Log the error if we are debugging
msg = 'Error occurred while evaluating CustomResponse' msg = 'Error occurred while evaluating CustomResponse'
...@@ -1498,11 +1505,11 @@ class CodeResponse(LoncapaResponse): ...@@ -1498,11 +1505,11 @@ class CodeResponse(LoncapaResponse):
queue_name = None queue_name = None
def setup_response(self): def setup_response(self):
''' """
Configure CodeResponse from XML. Supports both CodeResponse and ExternalResponse XML Configure CodeResponse from XML. Supports both CodeResponse and ExternalResponse XML
TODO: Determines whether in synchronous or asynchronous (queued) mode TODO: Determines whether in synchronous or asynchronous (queued) mode
''' """
xml = self.xml xml = self.xml
# TODO: XML can override external resource (grader/queue) URL # TODO: XML can override external resource (grader/queue) URL
self.url = xml.get('url', None) self.url = xml.get('url', None)
...@@ -1522,12 +1529,12 @@ class CodeResponse(LoncapaResponse): ...@@ -1522,12 +1529,12 @@ class CodeResponse(LoncapaResponse):
self._parse_coderesponse_xml(codeparam) self._parse_coderesponse_xml(codeparam)
def _parse_coderesponse_xml(self, codeparam): def _parse_coderesponse_xml(self, codeparam):
''' """
Parse the new CodeResponse XML format. When successful, sets: Parse the new CodeResponse XML format. When successful, sets:
self.initial_display self.initial_display
self.answer (an answer to display to the student in the LMS) self.answer (an answer to display to the student in the LMS)
self.payload self.payload
''' """
# Note that CodeResponse is agnostic to the specific contents of # Note that CodeResponse is agnostic to the specific contents of
# grader_payload # grader_payload
grader_payload = codeparam.find('grader_payload') grader_payload = codeparam.find('grader_payload')
...@@ -1614,9 +1621,10 @@ class CodeResponse(LoncapaResponse): ...@@ -1614,9 +1621,10 @@ class CodeResponse(LoncapaResponse):
cmap = CorrectMap() cmap = CorrectMap()
if error: if error:
cmap.set(self.answer_id, queuestate=None, _ = self.capa_system.i18n.ugettext
msg='Unable to deliver your submission to grader. (Reason: %s.)' error_msg = _('Unable to deliver your submission to grader (Reason: {error_msg}).'
' Please try again later.' % msg) ' Please try again later.').format(error_msg=msg)
cmap.set(self.answer_id, queuestate=None, msg=error_msg)
else: else:
# Queueing mechanism flags: # Queueing mechanism flags:
# 1) Backend: Non-null CorrectMap['queuestate'] indicates that # 1) Backend: Non-null CorrectMap['queuestate'] indicates that
...@@ -1632,9 +1640,13 @@ class CodeResponse(LoncapaResponse): ...@@ -1632,9 +1640,13 @@ class CodeResponse(LoncapaResponse):
def update_score(self, score_msg, oldcmap, queuekey): def update_score(self, score_msg, oldcmap, queuekey):
"""Updates the user's score based on the returned message from the grader.""" """Updates the user's score based on the returned message from the grader."""
(valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg) (valid_score_msg, correct, points, msg) = self._parse_score_msg(score_msg)
_ = self.capa_system.i18n.ugettext
if not valid_score_msg: if not valid_score_msg:
oldcmap.set(self.answer_id, # Translators: 'grader' refers to the edX automatic code grader.
msg='Invalid grader reply. Please contact the course staff.') error_msg = _('Invalid grader reply. Please contact the course staff.')
oldcmap.set(self.answer_id, msg=error_msg)
return oldcmap return oldcmap
correctness = 'correct' if correct else 'incorrect' correctness = 'correct' if correct else 'incorrect'
...@@ -1944,6 +1956,8 @@ class FormulaResponse(LoncapaResponse): ...@@ -1944,6 +1956,8 @@ class FormulaResponse(LoncapaResponse):
Each dictionary represents a test case for the answer. Each dictionary represents a test case for the answer.
Returns a tuple of formula evaluation results. Returns a tuple of formula evaluation results.
""" """
_ = self.capa_system.i18n.ugettext
out = [] out = []
for var_dict in var_dict_list: for var_dict in var_dict_list:
try: try:
...@@ -1959,7 +1973,7 @@ class FormulaResponse(LoncapaResponse): ...@@ -1959,7 +1973,7 @@ class FormulaResponse(LoncapaResponse):
cgi.escape(answer) cgi.escape(answer)
) )
raise StudentInputError( raise StudentInputError(
"Invalid input: " + err.message + " not permitted in answer" _("Invalid input: {bad_input} not permitted in answer.").format(bad_input=err.message)
) )
except ValueError as err: except ValueError as err:
if 'factorial' in err.message: if 'factorial' in err.message:
...@@ -1974,19 +1988,25 @@ class FormulaResponse(LoncapaResponse): ...@@ -1974,19 +1988,25 @@ class FormulaResponse(LoncapaResponse):
cgi.escape(answer) cgi.escape(answer)
) )
raise StudentInputError( raise StudentInputError(
("factorial function not permitted in answer " _("factorial function not permitted in answer "
"for this problem. Provided answer was: " "for this problem. Provided answer was: "
"{0}").format(cgi.escape(answer)) "{bad_input}").format(bad_input=cgi.escape(answer))
) )
# If non-factorial related ValueError thrown, handle it the same as any other Exception # If non-factorial related ValueError thrown, handle it the same as any other Exception
log.debug('formularesponse: error %s in formula', err) log.debug('formularesponse: error %s in formula', err)
raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % raise StudentInputError(
cgi.escape(answer)) _("Invalid input: Could not parse '{bad_input}' as a formula.").format(
bad_input=cgi.escape(answer)
)
)
except Exception as err: except Exception as err:
# traceback.print_exc() # traceback.print_exc()
log.debug('formularesponse: error %s in formula', err) log.debug('formularesponse: error %s in formula', err)
raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % raise StudentInputError(
cgi.escape(answer)) _("Invalid input: Could not parse '{bad_input}' as a formula").format(
bad_input=cgi.escape(answer)
)
)
return out return out
def randomize_variables(self, samples): def randomize_variables(self, samples):
...@@ -2124,7 +2144,9 @@ class SchematicResponse(LoncapaResponse): ...@@ -2124,7 +2144,9 @@ class SchematicResponse(LoncapaResponse):
unsafely=self.capa_system.can_execute_unsafe_code(), unsafely=self.capa_system.can_execute_unsafe_code(),
) )
except Exception as err: except Exception as err:
msg = 'Error %s in evaluating SchematicResponse' % err _ = self.capa_system.i18n.ugettext
# Translators: 'SchematicResponse' is a problem type and should not be translated.
msg = _('Error in evaluating SchematicResponse. The error was: {error_msg}').format(error_msg=err)
raise ResponseError(msg) raise ResponseError(msg)
cmap = CorrectMap() cmap = CorrectMap()
cmap.set_dict(dict(zip(sorted(self.answer_ids), self.context['correct']))) cmap.set_dict(dict(zip(sorted(self.answer_ids), self.context['correct'])))
...@@ -2674,6 +2696,7 @@ class ChoiceTextResponse(LoncapaResponse): ...@@ -2674,6 +2696,7 @@ class ChoiceTextResponse(LoncapaResponse):
Returns True if and only if all student inputs are correct. Returns True if and only if all student inputs are correct.
""" """
_ = self.capa_system.i18n.ugettext
inputs_correct = True inputs_correct = True
for answer_name, answer_value in numtolerance_inputs.iteritems(): for answer_name, answer_value in numtolerance_inputs.iteritems():
# If `self.corrrect_inputs` does not contain an entry for # If `self.corrrect_inputs` does not contain an entry for
...@@ -2691,11 +2714,11 @@ class ChoiceTextResponse(LoncapaResponse): ...@@ -2691,11 +2714,11 @@ class ChoiceTextResponse(LoncapaResponse):
correct_ans = complex(correct_ans) correct_ans = complex(correct_ans)
except ValueError: except ValueError:
log.debug( log.debug(
"Content error--answer" + "Content error--answer '%s' is not a valid complex number",
"'{0}' is not a valid complex number".format(correct_ans) correct_ans
) )
raise StudentInputError( raise StudentInputError(
"The Staff answer could not be interpreted as a number." _("The Staff answer could not be interpreted as a number.")
) )
# Compare the student answer to the staff answer/ or to 0 # Compare the student answer to the staff answer/ or to 0
# if all that is important is verifying numericality # if all that is important is verifying numericality
...@@ -2708,14 +2731,13 @@ class ChoiceTextResponse(LoncapaResponse): ...@@ -2708,14 +2731,13 @@ class ChoiceTextResponse(LoncapaResponse):
except: except:
# Use the traceback-preserving version of re-raising with a # Use the traceback-preserving version of re-raising with a
# different type # different type
_, _, trace = sys.exc_info() __, __, trace = sys.exc_info()
msg = _("Could not interpret '{given_answer}' as a number.").format(
raise StudentInputError( given_answer=cgi.escape(answer_value)
"Could not interpret '{0}' as a number{1}".format(
cgi.escape(answer_value),
trace
)
) )
msg += " ({0})".format(trace)
raise StudentInputError(msg)
# Ignore the results of the comparisons which were just for # Ignore the results of the comparisons which were just for
# Numerical Validation. # Numerical Validation.
if answer_name in self.correct_inputs and not partial_correct: if answer_name in self.correct_inputs and not partial_correct:
......
...@@ -313,7 +313,7 @@ class FileSubmissionTest(unittest.TestCase): ...@@ -313,7 +313,7 @@ class FileSubmissionTest(unittest.TestCase):
'STATIC_URL': '/dummy-static/', 'STATIC_URL': '/dummy-static/',
'id': 'prob_1_2', 'id': 'prob_1_2',
'status': 'queued', 'status': 'queued',
'msg': input_class.submitted_msg, 'msg': the_input.submitted_msg,
'value': 'BumbleBee.py', 'value': 'BumbleBee.py',
'queue_len': '3', 'queue_len': '3',
'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]', 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]',
...@@ -362,7 +362,7 @@ class CodeInputTest(unittest.TestCase): ...@@ -362,7 +362,7 @@ class CodeInputTest(unittest.TestCase):
'id': 'prob_1_2', 'id': 'prob_1_2',
'value': 'print "good evening"', 'value': 'print "good evening"',
'status': 'queued', 'status': 'queued',
'msg': input_class.submitted_msg, 'msg': the_input.submitted_msg,
'mode': mode, 'mode': mode,
'linenumbers': linenumbers, 'linenumbers': linenumbers,
'rows': rows, 'rows': rows,
...@@ -415,7 +415,7 @@ class MatlabTest(unittest.TestCase): ...@@ -415,7 +415,7 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2', 'id': 'prob_1_2',
'value': 'print "good evening"', 'value': 'print "good evening"',
'status': 'queued', 'status': 'queued',
'msg': self.input_class.submitted_msg, 'msg': self.the_input.submitted_msg,
'mode': self.mode, 'mode': self.mode,
'rows': self.rows, 'rows': self.rows,
'cols': self.cols, 'cols': self.cols,
...@@ -444,7 +444,7 @@ class MatlabTest(unittest.TestCase): ...@@ -444,7 +444,7 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2', 'id': 'prob_1_2',
'value': 'print "good evening"', 'value': 'print "good evening"',
'status': 'queued', 'status': 'queued',
'msg': self.input_class.submitted_msg, 'msg': the_input.submitted_msg,
'mode': self.mode, 'mode': self.mode,
'rows': self.rows, 'rows': self.rows,
'cols': self.cols, 'cols': self.cols,
...@@ -501,7 +501,7 @@ class MatlabTest(unittest.TestCase): ...@@ -501,7 +501,7 @@ class MatlabTest(unittest.TestCase):
'id': 'prob_1_2', 'id': 'prob_1_2',
'value': 'print "good evening"', 'value': 'print "good evening"',
'status': 'queued', 'status': 'queued',
'msg': self.input_class.plot_submitted_msg, 'msg': the_input.submitted_msg,
'mode': self.mode, 'mode': self.mode,
'rows': self.rows, 'rows': self.rows,
'cols': self.cols, 'cols': self.cols,
......
...@@ -1150,7 +1150,7 @@ class NumericalResponseTest(ResponseTest): ...@@ -1150,7 +1150,7 @@ class NumericalResponseTest(ResponseTest):
"""A fake gettext.Translations object.""" """A fake gettext.Translations object."""
def ugettext(self, text): def ugettext(self, text):
"""Return the 'translation' of `text`.""" """Return the 'translation' of `text`."""
if text == "There was a problem with the staff answer to this problem": if text == "There was a problem with the staff answer to this problem.":
text = "TRANSLATED!" text = "TRANSLATED!"
return text return text
problem.capa_system.i18n = FakeTranslations() problem.capa_system.i18n = FakeTranslations()
......
...@@ -8,10 +8,10 @@ default_tolerance = '0.001%' ...@@ -8,10 +8,10 @@ default_tolerance = '0.001%'
def compare_with_tolerance(v1, v2, tol=default_tolerance): def compare_with_tolerance(v1, v2, tol=default_tolerance):
''' """
Compare v1 to v2 with maximum tolerance tol. Compare v1 to v2 with maximum tolerance tol.
tol is relative if it ends in %; otherwise, it is absolute tol is relative if it ends in %; otherwise, it is absolute.
- v1 : student result (float complex number) - v1 : student result (float complex number)
- v2 : instructor result (float complex number) - v2 : instructor result (float complex number)
...@@ -26,7 +26,7 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance): ...@@ -26,7 +26,7 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
Out[183]: -3.3881317890172014e-21 Out[183]: -3.3881317890172014e-21
In [212]: 1.9e24 - 1.9*10**24 In [212]: 1.9e24 - 1.9*10**24
Out[212]: 268435456.0 Out[212]: 268435456.0
''' """
relative = tol.endswith('%') relative = tol.endswith('%')
if relative: if relative:
tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01 tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01
...@@ -46,8 +46,10 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance): ...@@ -46,8 +46,10 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
def contextualize_text(text, context): # private def contextualize_text(text, context): # private
''' Takes a string with variables. E.g. $a+$b. """
Does a substitution of those variables from the context ''' Takes a string with variables. E.g. $a+$b.
Does a substitution of those variables from the context
"""
if not text: if not text:
return text return text
for key in sorted(context, lambda x, y: cmp(len(y), len(x))): for key in sorted(context, lambda x, y: cmp(len(y), len(x))):
...@@ -66,10 +68,10 @@ def contextualize_text(text, context): # private ...@@ -66,10 +68,10 @@ def contextualize_text(text, context): # private
def convert_files_to_filenames(answers): def convert_files_to_filenames(answers):
''' """
Check for File objects in the dict of submitted answers, Check for File objects in the dict of submitted answers,
convert File objects to their filename (string) convert File objects to their filename (string)
''' """
new_answers = dict() new_answers = dict()
for answer_id in answers.keys(): for answer_id in answers.keys():
answer = answers[answer_id] answer = answers[answer_id]
...@@ -86,9 +88,9 @@ def is_list_of_files(files): ...@@ -86,9 +88,9 @@ def is_list_of_files(files):
def is_file(file_to_test): def is_file(file_to_test):
''' """
Duck typing to check if 'file_to_test' is a File object Duck typing to check if 'file_to_test' is a File object
''' """
return all(hasattr(file_to_test, method) for method in ['read', 'name']) return all(hasattr(file_to_test, method) for method in ['read', 'name'])
......
...@@ -12,9 +12,9 @@ dateformat = '%Y%m%d%H%M%S' ...@@ -12,9 +12,9 @@ dateformat = '%Y%m%d%H%M%S'
def make_hashkey(seed): def make_hashkey(seed):
''' """
Generate a string key by hashing Generate a string key by hashing
''' """
h = hashlib.md5() h = hashlib.md5()
h.update(str(seed)) h.update(str(seed))
return h.hexdigest() return h.hexdigest()
...@@ -57,9 +57,9 @@ def parse_xreply(xreply): ...@@ -57,9 +57,9 @@ def parse_xreply(xreply):
class XQueueInterface(object): class XQueueInterface(object):
''' """
Interface to the external grading system Interface to the external grading system
''' """
def __init__(self, url, django_auth, requests_auth=None): def __init__(self, url, django_auth, requests_auth=None):
self.url = url self.url = url
...@@ -106,8 +106,10 @@ class XQueueInterface(object): ...@@ -106,8 +106,10 @@ class XQueueInterface(object):
return self._http_post(self.url + '/xqueue/login/', payload) return self._http_post(self.url + '/xqueue/login/', payload)
def _send_to_queue(self, header, body, files_to_upload): def _send_to_queue(self, header, body, files_to_upload):
payload = {'xqueue_header': header, payload = {
'xqueue_body': body} 'xqueue_header': header,
'xqueue_body': body
}
files = {} files = {}
if files_to_upload is not None: if files_to_upload is not None:
for f in files_to_upload: for f in files_to_upload:
......
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