diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index 08a223f609b8..9228d66d6a40 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -7,11 +7,11 @@ # Each Response may have one or more Input entry fields. # The capa problem may include a solution. # -''' +""" Main module which shows problems (of "capa" type). This is used by capa_module. -''' +""" from datetime import datetime import logging @@ -68,12 +68,12 @@ class LoncapaProblem(object): - ''' + """ Main class for capa Problems. - ''' + """ def __init__(self, problem_text, id, state=None, seed=None, system=None): - ''' + """ Initializes capa Problem. Arguments: @@ -90,7 +90,7 @@ def __init__(self, problem_text, id, state=None, seed=None, system=None): - system (ModuleSystem): ModuleSystem instance which provides OS, rendering, and user context - ''' + """ ## Initialize class variables from state self.do_reset() @@ -143,9 +143,9 @@ def __init__(self, problem_text, id, state=None, seed=None, system=None): self.extracted_tree = self._extract_html(self.tree) def do_reset(self): - ''' + """ Reset internal state to unfinished, with no answers - ''' + """ self.student_answers = dict() self.correct_map = CorrectMap() self.done = False @@ -165,11 +165,11 @@ def __unicode__(self): return u"LoncapaProblem ({0})".format(self.problem_id) def get_state(self): - ''' + """ Stored per-user session data neeeded to: 1) Recreate the problem 2) Populate any student answers. - ''' + """ return {'seed': self.seed, 'student_answers': self.student_answers, @@ -178,9 +178,9 @@ def get_state(self): 'done': self.done} def get_max_score(self): - ''' + """ Return the maximum score for this problem. - ''' + """ maxscore = 0 for responder in self.responders.values(): maxscore += responder.get_max_score() @@ -197,7 +197,7 @@ def get_score(self): try: correct += self.correct_map.get_npoints(key) 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 if (not self.student_answers) or len(self.student_answers) == 0: @@ -208,12 +208,12 @@ def get_score(self): 'total': self.get_max_score()} def update_score(self, score_msg, queuekey): - ''' + """ Deliver grading response (e.g. from async code checking) to the specific ResponseType that requested grading Returns an updated CorrectMap - ''' + """ cmap = CorrectMap() cmap.update(self.correct_map) for responder in self.responders.values(): @@ -225,12 +225,12 @@ def update_score(self, score_msg, queuekey): return cmap def ungraded_response(self, xqueue_msg, queuekey): - ''' + """ 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 Does not return any value - ''' + """ # check against each inputtype for the_input in self.inputs.values(): # if the input type has an ungraded function, pass in the values @@ -238,17 +238,17 @@ def ungraded_response(self, xqueue_msg, queuekey): the_input.ungraded_response(xqueue_msg, queuekey) def is_queued(self): - ''' + """ Returns True if any part of the problem has been submitted to an external queue (e.g. for grading.) - ''' + """ return any(self.correct_map.is_queued(answer_id) for answer_id in self.correct_map) def get_recentmost_queuetime(self): - ''' + """ Returns a DateTime object that represents the timestamp of the most recent queueing request, or None if not queued - ''' + """ if not self.is_queued(): return None @@ -266,7 +266,7 @@ def get_recentmost_queuetime(self): return max(queuetimes) def grade_answers(self, answers): - ''' + """ 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 @@ -275,7 +275,7 @@ def grade_answers(self, answers): 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. - ''' + """ # if answers include File objects, convert them to filenames. self.student_answers = convert_files_to_filenames(answers) @@ -325,7 +325,6 @@ def _grade_answers(self, student_answers): # start new with empty CorrectMap newcmap = CorrectMap() - # Call each responsetype instance to do actual grading for responder in self.responders.values(): # File objects are passed only if responsetype explicitly allows @@ -334,7 +333,8 @@ def _grade_answers(self, student_answers): # an earlier submission, so for now skip these entirely. # TODO: figure out where to get file submissions when rescoring. if 'filesubmission' in responder.allowed_inputfields and student_answers is None: - raise Exception("Cannot rescore problems with possible file submissions") + _ = self.system.service(self, "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 # submission that would not exist in the persisted "student_answers". @@ -366,7 +366,7 @@ def get_question_answers(self): if answer: 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 def get_answer_ids(self): @@ -382,18 +382,18 @@ def get_answer_ids(self): return answer_ids def get_html(self): - ''' + """ 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) return html def handle_input_ajax(self, 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 - ''' + """ # pull out the id input_id = data['input_id'] @@ -401,16 +401,16 @@ def handle_input_ajax(self, data): dispatch = data['dispatch'] return self.inputs[input_id].handle_ajax(dispatch, data) 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 {} # ======= Private Methods Below ======== def _process_includes(self): - ''' + """ Handle any tags by reading in the specified file and inserting it into our XML tree. Fail gracefully if debugging. - ''' + """ includes = self.tree.findall('.//include') for inc in includes: filename = inc.get('file') @@ -420,14 +420,12 @@ def _process_includes(self): ifp = self.system.filestore.open(filename) except Exception as err: log.warning( - 'Error %s in problem xml include: %s' % ( - err, etree.tostring(inc, pretty_print=True) - ) + 'Error %s in problem xml include: %s', + err, + etree.tostring(inc, pretty_print=True) ) log.warning( - 'Cannot find file %s in %s' % ( - filename, self.system.filestore - ) + 'Cannot find file %s in %s', filename, self.system.filestore ) # if debugging, don't fail - just log error # TODO (vshnayder): need real error handling, display to users @@ -440,11 +438,11 @@ def _process_includes(self): incxml = etree.XML(ifp.read()) except Exception as err: log.warning( - 'Error %s in problem xml include: %s' % ( - err, etree.tostring(inc, pretty_print=True) - ) + 'Error %s in problem xml include: %s', + 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 # TODO (vshnayder): same as above if not self.system.get('DEBUG'): @@ -484,7 +482,7 @@ def _extract_system_path(self, script): # Check that we are within the filestore tree. reldir = os.path.relpath(dir, self.system.filestore.root_path) if ".." in reldir: - log.warning("Ignoring Python directory outside of course: %r" % dir) + log.warning("Ignoring Python directory outside of course: %r", dir) continue abs_dir = os.path.normpath(dir) @@ -493,13 +491,13 @@ def _extract_system_path(self, script): return path def _extract_context(self, tree): - ''' + """ Extract content of from the problem.xml file, and exec it in the context of this problem. Provides ability to randomize problems, and also set variables for problem answer checking. Problem XML goes to Python execution context. Runs everything in script tags. - ''' + """ context = {} context['seed'] = self.seed all_code = '' @@ -546,7 +544,7 @@ def _extract_context(self, tree): return context def _extract_html(self, problemtree): # private - ''' + """ Main (private) function which converts Problem XML tree to HTML. Calls itself recursively. @@ -554,7 +552,7 @@ def _extract_html(self, problemtree): # private Calls render_html of Response instances to render responses into XHTML. Used by get_html. - ''' + """ if not isinstance(problemtree.tag, basestring): # Comment and ProcessingInstruction nodes are not Elements, # and we're ok leaving those behind. @@ -594,13 +592,17 @@ def _extract_html(self, problemtree): # private self.input_state[input_id] = {} # do the rendering - state = {'value': value, - 'status': status, - 'id': input_id, - 'input_state': self.input_state[input_id], - 'feedback': {'message': msg, - 'hint': hint, - 'hintmode': hintmode, }} + state = { + 'value': value, + 'status': status, + 'id': input_id, + 'input_state': self.input_state[input_id], + 'feedback': { + 'message': msg, + 'hint': hint, + 'hintmode': hintmode, + } + } 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 @@ -640,7 +642,7 @@ def _extract_html(self, problemtree): # private return tree def _preprocess_problem(self, tree): # private - ''' + """ Assign IDs to all the responses Assign sub-IDs to all entries (textline, schematic, etc.) Annoted correctness and value @@ -649,7 +651,7 @@ def _preprocess_problem(self, tree): # private 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) - ''' + """ response_id = 1 self.responders = {} for response in tree.xpath('//' + "|//".join(response_tag_dict)): diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index e50be92152b5..2ea4a35c024d 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -63,13 +63,13 @@ def __repr__(self): return repr(self.cmap) def get_dict(self): - ''' + """ return dict version of self - ''' + """ return self.cmap def set_dict(self, correct_map): - ''' + """ 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 @@ -85,7 +85,7 @@ def set_dict(self, correct_map): Special migration case: If correct_map is a one-level dict, then convert it to the new dict of dicts format. - ''' + """ # empty current dict self.__init__() @@ -149,17 +149,17 @@ def get_hintmode(self, answer_id): return self.get_property(answer_id, 'hintmode', None) def set_hint_and_mode(self, answer_id, hint, hintmode): - ''' + """ - hint : (string) HTML text for hint - hintmode : (string) mode for hint display ('always' or 'on_request') - ''' + """ self.set_property(answer_id, 'hint', hint) self.set_property(answer_id, 'hintmode', hintmode) def update(self, other_cmap): - ''' + """ Update this CorrectMap with the contents of another CorrectMap - ''' + """ if not isinstance(other_cmap, CorrectMap): raise Exception('CorrectMap.update called with invalid argument %s' % other_cmap) self.cmap.update(other_cmap.get_dict()) diff --git a/common/lib/capa/capa/customrender.py b/common/lib/capa/capa/customrender.py index f7d586c9d55d..9203de9449c6 100644 --- a/common/lib/capa/capa/customrender.py +++ b/common/lib/capa/capa/customrender.py @@ -26,7 +26,7 @@ class MathRenderer(object): tags = ['math'] def __init__(self, system, xml): - r''' + r""" Render math using latex-like formatting. Examples: @@ -37,7 +37,7 @@ def __init__(self, system, xml): We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline] TODO: use shorter tags (but this will require converting problem XML files!) - ''' + """ self.system = system self.xml = xml @@ -79,13 +79,13 @@ def get_html(self): class SolutionRenderer(object): - ''' + """ A solution is just a ... which is given an ID, that is used for displaying an 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 ajax call. - ''' + """ tags = ['solution'] def __init__(self, system, xml): diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 9dfd50836fbc..1184e7905acf 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -102,7 +102,8 @@ def parse_from_xml(self, element): val = element.get(self.name) if self.default == self._sentinel and val is None: raise ValueError( - 'Missing required attribute {0}.'.format(self.name)) + 'Missing required attribute {0}.'.format(self.name) + ) if val is None: # not required, so return default @@ -156,8 +157,9 @@ def __init__(self, system, xml, state): self.input_id = state.get('id', xml.get('id')) if self.input_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', '') @@ -259,8 +261,9 @@ def _get_render_context(self): 'msg': self.msg, 'STATIC_URL': self.system.STATIC_URL, } - 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 @@ -390,7 +393,7 @@ def _extra_context(self): @staticmethod def extract_choices(element): - ''' + """ Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and CheckboxGroup. @@ -398,7 +401,7 @@ def extract_choices(element): TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute, ie random, top, bottom. - ''' + """ choices = [] @@ -575,7 +578,8 @@ def setup(self): # Preprocessor to insert between raw input and Mathjax self.preprocessor = { 'class_name': self.loaded_attributes['preprocessorClassName'], - 'script_src': self.loaded_attributes['preprocessorSrc']} + 'script_src': self.loaded_attributes['preprocessorSrc'], + } if None in self.preprocessor.values(): self.preprocessor = None @@ -596,9 +600,6 @@ class FileSubmission(InputTypeBase): template = "filesubmission.html" 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 def parse_files(files): @@ -620,6 +621,11 @@ def setup(self): 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). """ + _ = self.system.service(self, "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 self.queue_len = 0 # Flag indicating that the problem has been queued, 'msg' is length of @@ -627,7 +633,7 @@ def setup(self): if self.status == 'incomplete': self.status = 'queued' self.queue_len = self.msg - self.msg = FileSubmission.submitted_msg + self.msg = self.submitted_msg def _extra_context(self): return {'queue_len': self.queue_len, } @@ -644,15 +650,13 @@ 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. - ] + tags = [ + 'codeinput', + 'textbox', + # Another (older) name--at some point we may want to make it use a + # 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 def get_attributes(cls): @@ -668,7 +672,7 @@ def get_attributes(cls): Attribute('linenumbers', 'true'), # Template expects tabsize to be an int it can do math with Attribute('tabsize', 4, transform=int), - ] + ] def setup_code_response_rendering(self): """ @@ -689,7 +693,12 @@ def setup_code_response_rendering(self): self.msg = self.submitted_msg def setup(self): - ''' setup this input type ''' + """ setup this input type """ + _ = self.system.service(self, "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() def _extra_context(self): @@ -703,7 +712,7 @@ def _extra_context(self): class MatlabInput(CodeInput): - ''' + """ InputType for handling Matlab code input TODO: API_KEY will go away once we have a way to specify it per-course @@ -714,17 +723,20 @@ class MatlabInput(CodeInput): %api_key=API_KEY - ''' + """ template = "matlabinput.html" tags = ['matlabinput'] - plot_submitted_msg = ("Submitted. As soon as a response is returned, " - "this message will be replaced by that feedback.") def setup(self): - ''' + """ Handle matlab-specific parsing - ''' + """ + _ = self.system.service(self, "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() xml = self.xml @@ -740,10 +752,10 @@ def setup(self): if 'queuestate' in self.input_state and self.input_state['queuestate'] == 'queued': self.status = 'queued' self.queue_len = 1 - self.msg = self.plot_submitted_msg + self.msg = self.submitted_msg def handle_ajax(self, dispatch, data): - ''' + """ Handle AJAX calls directed to this input Args: @@ -752,14 +764,14 @@ def handle_ajax(self, dispatch, data): Returns: dict - 'success' - whether or not we successfully queued this submission - 'message' - message to be rendered in case of error - ''' + """ if dispatch == 'plot': return self._plot_data(data) return {} def ungraded_response(self, queue_msg, queuekey): - ''' + """ Handle the response from the XQueue Stores the response in the input_state so it can be rendered later @@ -769,7 +781,7 @@ def ungraded_response(self, queue_msg, queuekey): Returns: nothing - ''' + """ # check the queuekey against the saved queuekey if('queuestate' in self.input_state and self.input_state['queuestate'] == 'queued' and self.input_state['queuekey'] == queuekey): @@ -791,7 +803,7 @@ def button_enabled(self): return True def _extra_context(self): - ''' Set up additional context variables''' + """ Set up additional context variables""" extra_context = { 'queue_len': str(self.queue_len), 'queue_msg': self.queue_msg, @@ -800,31 +812,31 @@ def _extra_context(self): return extra_context def _parse_data(self, queue_msg): - ''' + """ Parses the message out of the queue message Args: queue_msg (str) - a JSON encoded string Returns: returns the value for the the key 'msg' in queue_msg - ''' + """ try: result = json.loads(queue_msg) except (TypeError, ValueError): log.error("External message should be a JSON serialized dict." - " Received queue_msg = %s" % queue_msg) + " Received queue_msg = %s", queue_msg) raise msg = result['msg'] return msg def _plot_data(self, data): - ''' + """ AJAX handler for the plot button Args: get (dict) - should have key 'submission' which contains the student submission Returns: dict - 'success' - whether or not we successfully queued this submission - 'message' - message to be rendered in case of error - ''' + """ # only send data if xqueue exists if self.system.xqueue is None: return {'success': False, 'message': 'Cannot connect to the queue'} @@ -846,11 +858,15 @@ def _plot_data(self, data): queue_name=self.queuename) # construct xqueue body - student_info = {'anonymous_student_id': anonymous_student_id, - 'submission_time': qtime} - contents = {'grader_payload': self.plot_payload, - 'student_info': json.dumps(student_info), - 'student_response': response} + student_info = { + 'anonymous_student_id': anonymous_student_id, + 'submission_time': qtime + } + contents = { + 'grader_payload': self.plot_payload, + 'student_info': json.dumps(student_info), + 'student_response': response + } (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) @@ -886,7 +902,8 @@ def get_attributes(cls): Attribute('parts', None), Attribute('analyses', None), Attribute('initial_value', None), - Attribute('submit_analyses', None), ] + Attribute('submit_analyses', None), + ] registry.register(Schematic) @@ -1020,10 +1037,10 @@ def _extra_context(self): } def handle_ajax(self, dispatch, data): - ''' + """ Since we only have chemcalc preview this input, check to see if it matches the corresponding dispatch and send it through if it does - ''' + """ if dispatch == 'preview_chemcalc': return self.preview_chemcalc(data) return {} @@ -1107,10 +1124,10 @@ def _extra_context(self): } def handle_ajax(self, dispatch, get): - ''' + """ Since we only have formcalc preview this input, check to see if it matches the corresponding dispatch and send it through if it does - ''' + """ if dispatch == 'preview_formcalc': return self.preview_formcalc(get) return {} @@ -1422,7 +1439,7 @@ def setup(self): self._validate_options() 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') return [{ 'id': index, @@ -1431,7 +1448,7 @@ def _find_options(self): } for (index, option) in enumerate(elements)] 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') for option in self.options: choice = option['choice'] @@ -1442,7 +1459,7 @@ def _validate_options(self): choice, ', '.join(valid_choices))) 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) if type(d) != dict: d = {} diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 4d36d44f9ef2..f7ad18339019 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1,12 +1,12 @@ # # File: courseware/capa/responsetypes.py # -''' +""" Problem response evaluation. Handles checking of student responses, of a variety of types. Used by capa_problem.py -''' +""" # standard library imports import abc @@ -54,25 +54,25 @@ class LoncapaProblemError(Exception): - ''' + """ Error in specification of a problem - ''' + """ pass class ResponseError(Exception): - ''' + """ Error for failure in processing a response, including exceptions that occur when executing a custom script. - ''' + """ pass class StudentInputError(Exception): - ''' + """ Error for an invalid student input. For example, submitting a string when the problem expects a number - ''' + """ pass #----------------------------------------------------------------------------- @@ -127,7 +127,7 @@ class LoncapaResponse(object): required_attributes = [] def __init__(self, xml, inputfields, context, system=None): - ''' + """ Init is passed the following arguments: - xml : ElementTree of this Response @@ -135,7 +135,7 @@ def __init__(self, xml, inputfields, context, system=None): - context : script processor context - system : ModuleSystem instance which provides OS, rendering, and user context - ''' + """ self.xml = xml self.inputfields = inputfields self.context = context @@ -143,6 +143,8 @@ def __init__(self, xml, inputfields, context, system=None): 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: if abox.tag not in self.allowed_inputfields: msg = "%s: cannot have input field %s" % ( @@ -191,20 +193,20 @@ def __init__(self, xml, inputfields, context, system=None): self.setup_response() def get_max_score(self): - ''' + """ Return the total maximum points of all answer fields under this Response - ''' + """ return sum(self.maxpoints.values()) def render_html(self, renderer, response_msg=''): - ''' + """ Return XHTML Element tree representation of this Response. Arguments: - renderer : procedure which produces HTML given an ElementTree - response_msg: a message displayed at the end of the Response - ''' + """ # render ourself as a + our content tree = etree.Element('span') @@ -226,12 +228,12 @@ def render_html(self, renderer, response_msg=''): return tree def evaluate_answers(self, student_answers, old_cmap): - ''' + """ Called by capa_problem.LoncapaProblem to evaluate student answers, and to generate hints (if any). 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) @@ -239,14 +241,14 @@ def evaluate_answers(self, student_answers, old_cmap): return new_cmap def get_hints(self, student_answers, new_cmap, old_cmap): - ''' + """ Generate adaptive hints for this problem based on student answers, the old CorrectMap, and the new CorrectMap produced by get_score. Does not return anything. Modifies new_cmap, by adding hints to answer_id entries as appropriate. - ''' + """ hintgroup = self.xml.find('hintgroup') if hintgroup is None: return @@ -298,9 +300,10 @@ def get_hints(self, student_answers, new_cmap, old_cmap): unsafely=self.system.can_execute_unsafe_code(), ) except Exception as err: - msg = 'Error %s in evaluating hint function %s' % (err, hintfn) - msg += "\nSee XML source line %s" % getattr( - self.xml, 'sourceline', '') + _ = self.system.service(self, "i18n").ugettext + msg = _('Error {err} in evaluating hint function {hintfn}.').format(err=err, hintfn=hintfn) + sourcenum = getattr(self.xml, 'sourceline', _('(Source code line unavailable)')) + msg += "\n" + _("See XML source line {sourcenum}.").format(sourcenum=sourcenum) raise ResponseError(msg) new_cmap.set_dict(globals_dict['new_cmap_dict']) @@ -343,24 +346,24 @@ def get_hints(self, student_answers, new_cmap, old_cmap): @abc.abstractmethod def get_score(self, student_answers): - ''' + """ Return a CorrectMap for the answers expected vs given. This includes (correctness, npoints, msg) for each answer_id. Arguments: - student_answers : dict of (answer_id, answer) where answer = student input (string) - ''' + """ pass @abc.abstractmethod def get_answers(self): - ''' + """ Return a dict of (answer_id, answer_text) for each answer for this question. - ''' + """ pass def check_hint_condition(self, hxml_set, student_answers): - ''' + """ Return a list of hints to show. - hxml_set : list of Element trees, each specifying a condition to be @@ -370,7 +373,7 @@ def check_hint_condition(self, hxml_set, student_answers): Returns a list of names of hint conditions which were satisfied. Those are used to determine which hints are displayed. - ''' + """ pass def setup_response(self): @@ -668,9 +671,9 @@ def setup_response(self): 'name') for choice in correct_xml]) def assign_choice_names(self): - ''' + """ Initialize name attributes in tags for this response. - ''' + """ for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice', id=self.xml.get('id'))): @@ -723,12 +726,13 @@ def setup_response(self): self.correct_choices = [ contextualize_text(choice.get('name'), self.context) 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): - ''' + """ Initialize name attributes in stanzas in the in this response. - ''' + """ i = 0 for response in self.xml.xpath("choicegroup"): rtype = response.get('type') @@ -743,9 +747,9 @@ def mc_setup_response(self): choice.set("name", "choice_" + choice.get("name")) def get_score(self, student_answers): - ''' + """ grade student response. - ''' + """ # log.debug('%s: student_answers=%s, correct_choices=%s' % ( # unicode(self), student_answers, self.correct_choices)) if (self.answer_id in student_answers @@ -786,9 +790,9 @@ def get_score(self, student_answers): class OptionResponse(LoncapaResponse): - ''' + """ TODO: handle direction and randomize - ''' + """ response_tag = 'optionresponse' hint_tag = 'optionhint' @@ -819,10 +823,10 @@ def get_answers(self): class NumericalResponse(LoncapaResponse): - ''' + """ 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. - ''' + """ response_tag = 'numericalresponse' hint_tag = 'numericalhint' @@ -866,20 +870,22 @@ def get_staff_ans(self): correct_ans = evaluator({}, {}, self.correct_answer) except Exception: log.debug("Content error--answer '%s' is not a valid number", self.correct_answer) + _ = self.system.service(self, "i18n").ugettext 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 def get_score(self, student_answers): - '''Grade a numeric response ''' + """Grade a numeric response""" student_answer = student_answers[self.answer_id] correct_float = self.get_staff_ans() + _ = self.system.service(self, "i18n").ugettext 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 @@ -888,7 +894,7 @@ def get_score(self, student_answers): student_float = evaluator({}, {}, student_answer) except UndefinedVariable as undef_var: 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: if 'factorial' in val_err.message: @@ -897,14 +903,14 @@ def get_score(self, student_answers): # ve.message will be: `factorial() only accepts integral values` or # `factorial() not defined for negative values` raise StudentInputError( - ("factorial function evaluated outside its domain:" - "'{0}'").format(cgi.escape(student_answer)) + _("factorial function evaluated outside its domain:" + "'{student_answer}'").format(student_answer=cgi.escape(student_answer)) ) else: raise general_exception except ParseException: 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: raise general_exception @@ -946,7 +952,7 @@ def get_answers(self): class StringResponse(LoncapaResponse): - ''' + """ This response type allows one or more answers. Use `_or_` separator to set more than 1 answer. @@ -962,7 +968,7 @@ class StringResponse(LoncapaResponse): - ''' + """ response_tag = 'stringresponse' hint_tag = 'stringhint' allowed_inputfields = ['textline'] @@ -976,7 +982,7 @@ def setup_response(self): for answer in self.xml.get('answer').split(self.SEPARATOR)] def get_score(self, student_answers): - '''Grade a string response ''' + """Grade a string response """ student_answer = student_answers[self.answer_id].strip() correct = self.check_string(self.correct_answer, student_answer) return CorrectMap(self.answer_id, 'correct' if correct else 'incorrect') @@ -1007,10 +1013,10 @@ def get_answers(self): class CustomResponse(LoncapaResponse): - ''' + """ Custom response. The python code to be run should be in ... or in a - ''' + """ response_tag = 'customresponse' @@ -1091,10 +1097,10 @@ def check_function(expect, ans, **kwargs): self.code = answer.text def get_score(self, student_answers): - ''' + """ student_answers is a dict with everything from request.POST, but with the first part of each key removed (the string before the first "_"). - ''' + """ log.debug('%s: student_answers=%s', unicode(self), student_answers) @@ -1260,8 +1266,10 @@ def execute_check_function(self, idset, submission): # Raise an exception else: log.error(traceback.format_exc()) + _ = self.system.service(self, "i18n").ugettext raise ResponseError( - "CustomResponse: check function returned an invalid dict") + _("CustomResponse: check function returned an invalid dictionary!") + ) else: correct = ['correct' if ret else 'incorrect'] * len(idset) @@ -1300,7 +1308,7 @@ def clean_message_html(self, msg): return "" def get_answers(self): - ''' + """ Give correct answer expected for this response. use default_answer_map from entry elements (eg textline), @@ -1308,7 +1316,7 @@ def get_answers(self): but for simplicity, if an "expect" attribute was given by the content author ie then that. - ''' + """ if len(self.answer_ids) > 1: return self.default_answer_map if self.expect: @@ -1316,12 +1324,12 @@ def get_answers(self): return self.default_answer_map def _handle_exec_exception(self, err): - ''' + """ Handle an exception raised during the execution of custom Python code. Raises a ResponseError - ''' + """ # Log the error if we are debugging msg = 'Error occurred while evaluating CustomResponse' @@ -1408,11 +1416,11 @@ class CodeResponse(LoncapaResponse): queue_name = None def setup_response(self): - ''' + """ Configure CodeResponse from XML. Supports both CodeResponse and ExternalResponse XML TODO: Determines whether in synchronous or asynchronous (queued) mode - ''' + """ xml = self.xml # TODO: XML can override external resource (grader/queue) URL self.url = xml.get('url', None) @@ -1432,12 +1440,12 @@ def setup_response(self): self._parse_coderesponse_xml(codeparam) def _parse_coderesponse_xml(self, codeparam): - ''' + """ Parse the new CodeResponse XML format. When successful, sets: self.initial_display self.answer (an answer to display to the student in the LMS) self.payload - ''' + """ # Note that CodeResponse is agnostic to the specific contents of # grader_payload grader_payload = codeparam.find('grader_payload') @@ -1524,9 +1532,10 @@ def get_score(self, student_answers): cmap = CorrectMap() if error: - cmap.set(self.answer_id, queuestate=None, - msg='Unable to deliver your submission to grader. (Reason: %s.)' - ' Please try again later.' % msg) + _ = self.system.service(self, "i18n").ugettext + error_msg = _('Unable to deliver your submission to grader (Reason: {error_msg}).' + ' Please try again later.').format(error_msg=msg) + cmap.set(self.answer_id, queuestate=None, msg=error_msg) else: # Queueing mechanism flags: # 1) Backend: Non-null CorrectMap['queuestate'] indicates that @@ -1542,9 +1551,13 @@ def get_score(self, student_answers): def update_score(self, score_msg, oldcmap, queuekey): """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) + + _ = self.system.service(self, "i18n").ugettext + if not valid_score_msg: - oldcmap.set(self.answer_id, - msg='Invalid grader reply. Please contact the course staff.') + # Translators: 'grader' refers to the edX automatic code grader. + error_msg = _('Invalid grader reply. Please contact the course staff.') + oldcmap.set(self.answer_id, msg=error_msg) return oldcmap correctness = 'correct' if correct else 'incorrect' @@ -1852,6 +1865,8 @@ def tupleize_answers(self, answer, var_dict_list): Each dictionary represents a test case for the answer. Returns a tuple of formula evaluation results. """ + _ = self.system.service(self, "i18n").ugettext + out = [] for var_dict in var_dict_list: try: @@ -1867,7 +1882,7 @@ def tupleize_answers(self, answer, var_dict_list): cgi.escape(answer) ) 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: if 'factorial' in err.message: @@ -1882,19 +1897,23 @@ def tupleize_answers(self, answer, var_dict_list): cgi.escape(answer) ) raise StudentInputError( - ("factorial function not permitted in answer " - "for this problem. Provided answer was: " - "{0}").format(cgi.escape(answer)) + _("factorial function not permitted in answer " + "for this problem. Provided answer was: " + "{bad_input}").format(bad_input=cgi.escape(answer)) ) # If non-factorial related ValueError thrown, handle it the same as any other Exception log.debug('formularesponse: error %s in formula', err) - raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % - cgi.escape(answer)) + raise StudentInputError( + _("Invalid input: Could not parse '{bad_input}' as a formula.").format(cgi.escape(answer)) + ) except Exception as err: # traceback.print_exc() log.debug('formularesponse: error %s in formula', err) - raise StudentInputError("Invalid input: Could not parse '%s' as a formula" % - cgi.escape(answer)) + raise StudentInputError( + _("Invalid input: Could not parse '{bad_input}' as a formula").format( + bad_input=cgi.escape(answer) + ) + ) return out def randomize_variables(self, samples): @@ -2031,7 +2050,9 @@ def get_score(self, student_answers): unsafely=self.system.can_execute_unsafe_code(), ) except Exception as err: - msg = 'Error %s in evaluating SchematicResponse' % err + _ = self.system.service(self, "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) cmap = CorrectMap() cmap.set_dict(dict(zip(sorted(self.answer_ids), self.context['correct']))) @@ -2578,6 +2599,7 @@ def _check_student_inputs(self, numtolerance_inputs): Returns True if and only if all student inputs are correct. """ + _ = self.system.service(self, "i18n").ugettext inputs_correct = True for answer_name, answer_value in numtolerance_inputs.iteritems(): # If `self.corrrect_inputs` does not contain an entry for @@ -2595,11 +2617,11 @@ def _check_student_inputs(self, numtolerance_inputs): correct_ans = complex(correct_ans) except ValueError: log.debug( - "Content error--answer" + - "'{0}' is not a valid complex number".format(correct_ans) + "Content error--answer '%s' is not a valid complex number", + correct_ans ) 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 # if all that is important is verifying numericality @@ -2612,14 +2634,13 @@ def _check_student_inputs(self, numtolerance_inputs): except: # Use the traceback-preserving version of re-raising with a # different type - _, _, trace = sys.exc_info() - - raise StudentInputError( - "Could not interpret '{0}' as a number{1}".format( - cgi.escape(answer_value), - trace - ) + __, __, trace = sys.exc_info() + msg = _("Could not interpret '{given_answer}' as a number.").format( + given_answer=cgi.escape(answer_value) ) + msg += " ({0})".format(trace) + raise StudentInputError(msg) + # Ignore the results of the comparisons which were just for # Numerical Validation. if answer_name in self.correct_inputs and not partial_correct: diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py index 7cc8a85a4a62..6c508cdd2cd2 100644 --- a/common/lib/capa/capa/tests/__init__.py +++ b/common/lib/capa/capa/tests/__init__.py @@ -51,6 +51,9 @@ def test_system(): cache=None, can_execute_unsafe_code=lambda: False, ) + + # Mock out i18n to just return the string that is passed in + the_system.service("i18n").ugettext = lambda x: x return the_system diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py index 6d7a8e1ce9b6..b2fd51991ec1 100644 --- a/common/lib/capa/capa/tests/test_inputtypes.py +++ b/common/lib/capa/capa/tests/test_inputtypes.py @@ -309,7 +309,7 @@ def test_rendering(self): 'STATIC_URL': '/dummy-static/', 'id': 'prob_1_2', 'status': 'queued', - 'msg': input_class.submitted_msg, + 'msg': the_input.submitted_msg, 'value': 'BumbleBee.py', 'queue_len': '3', 'allowed_files': '["runme.py", "nooooo.rb", "ohai.java"]', @@ -358,7 +358,7 @@ def test_rendering(self): 'id': 'prob_1_2', 'value': 'print "good evening"', 'status': 'queued', - 'msg': input_class.submitted_msg, + 'msg': the_input.submitted_msg, 'mode': mode, 'linenumbers': linenumbers, 'rows': rows, @@ -411,7 +411,7 @@ def test_rendering(self): 'id': 'prob_1_2', 'value': 'print "good evening"', 'status': 'queued', - 'msg': self.input_class.submitted_msg, + 'msg': self.the_input.submitted_msg, 'mode': self.mode, 'rows': self.rows, 'cols': self.cols, @@ -440,7 +440,7 @@ def test_rendering_with_state(self): 'id': 'prob_1_2', 'value': 'print "good evening"', 'status': 'queued', - 'msg': self.input_class.submitted_msg, + 'msg': the_input.submitted_msg, 'mode': self.mode, 'rows': self.rows, 'cols': self.cols, @@ -497,7 +497,7 @@ def test_rendering_while_queued(self): 'id': 'prob_1_2', 'value': 'print "good evening"', 'status': 'queued', - 'msg': self.input_class.plot_submitted_msg, + 'msg': the_input.submitted_msg, 'mode': self.mode, 'rows': self.rows, 'cols': self.cols, diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py index 433e99171d0c..f92f83fbaa08 100644 --- a/common/lib/capa/capa/util.py +++ b/common/lib/capa/capa/util.py @@ -7,14 +7,15 @@ def compare_with_tolerance(v1, v2, tol): - ''' Compare v1 to v2 with maximum tolerance tol - tol is relative if it ends in %; otherwise, it is absolute + """ + Compare v1 to v2 with maximum tolerance `tol`. + `tol` is relative if it ends in %; otherwise, it is absolute. - v1 : student result (number) - v2 : instructor result (number) - tol : tolerance (string representing a number) - ''' + """ relative = tol.endswith('%') if relative: tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01 @@ -32,8 +33,10 @@ def compare_with_tolerance(v1, v2, tol): 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: return text for key in sorted(context, lambda x, y: cmp(len(y), len(x))): @@ -52,10 +55,10 @@ def contextualize_text(text, context): # private def convert_files_to_filenames(answers): - ''' + """ Check for File objects in the dict of submitted answers, convert File objects to their filename (string) - ''' + """ new_answers = dict() for answer_id in answers.keys(): answer = answers[answer_id] @@ -72,9 +75,9 @@ def is_list_of_files(files): def is_file(file_to_test): - ''' + """ Duck typing to check if 'file_to_test' is a File object - ''' + """ return all(hasattr(file_to_test, method) for method in ['read', 'name']) diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py index 81a221961dc3..270b696f9f40 100644 --- a/common/lib/capa/capa/xqueue_interface.py +++ b/common/lib/capa/capa/xqueue_interface.py @@ -12,9 +12,9 @@ def make_hashkey(seed): - ''' + """ Generate a string key by hashing - ''' + """ h = hashlib.md5() h.update(str(seed)) return h.hexdigest() @@ -57,9 +57,9 @@ def parse_xreply(xreply): class XQueueInterface(object): - ''' + """ Interface to the external grading system - ''' + """ def __init__(self, url, django_auth, requests_auth=None): self.url = url @@ -106,8 +106,10 @@ def _login(self): return self._http_post(self.url + '/xqueue/login/', payload) def _send_to_queue(self, header, body, files_to_upload): - payload = {'xqueue_header': header, - 'xqueue_body': body} + payload = { + 'xqueue_header': header, + 'xqueue_body': body + } files = {} if files_to_upload is not None: for f in files_to_upload: diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 60bf55b9f3c8..971d494411bd 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -497,27 +497,32 @@ def handle_problem_html_error(self, err): if answer_id.find(hidden_state_keyword) >= 0: student_answers.pop(answer_id) - # Next, generate a fresh LoncapaProblem + # Next, generate a fresh LoncapaProblem self.lcp = self.new_lcp(None) self.set_state_from_lcp() # Prepend a scary warning to the student - warning = '
'\ - '

Warning: The problem has been reset to its initial state!

'\ - 'The problem\'s state was corrupted by an invalid submission. ' \ - 'The submission consisted of:'\ - '
    ' + _ = self.runtime.service(self, "i18n").ugettext + warning_msg = _("Warning: The problem has been reset to its initial state!") + warning = '

    ' + warning_msg + '

    ' + + # Translators: Following this message, there will be a bulleted list of items. + warning_msg = _("The problem's state was corrupted by an invalid submission. The submission consisted of:") + warning += warning_msg + '
      ' + for student_answer in student_answers.values(): if student_answer != '': warning += '
    • ' + cgi.escape(student_answer) + '
    • ' - warning += '
    '\ - 'If this error persists, please contact the course staff.'\ - '
    ' + + warning_msg = _('If this error persists, please contact the course staff.') + warning += '
' + warning_msg + '
' html = warning + try: html += self.lcp.get_html() - except Exception: # Couldn't do it. Give up + except Exception: # pylint: disable=broad-except + # Couldn't do it. Give up log.exception("Unable to generate html from LoncapaProblem") raise @@ -546,20 +551,22 @@ def get_problem_html(self, encapsulate=True): else: check_button = False - content = {'name': self.display_name_with_default, - 'html': html, - 'weight': self.weight, - } - - context = {'problem': content, - 'id': self.id, - 'check_button': check_button, - 'reset_button': self.should_show_reset_button(), - 'save_button': self.should_show_save_button(), - 'answer_available': self.answer_available(), - 'attempts_used': self.attempts, - 'attempts_allowed': self.max_attempts, - } + content = { + 'name': self.display_name_with_default, + 'html': html, + 'weight': self.weight, + } + + context = { + 'problem': content, + 'id': self.id, + 'check_button': check_button, + 'reset_button': self.should_show_reset_button(), + 'save_button': self.should_show_save_button(), + 'answer_available': self.answer_available(), + 'attempts_used': self.attempts, + 'attempts_allowed': self.max_attempts, + } html = self.system.render_template('problem.html', context) @@ -568,7 +575,7 @@ def get_problem_html(self, encapsulate=True): id=self.location.html_id(), ajax_url=self.system.ajax_url ) + html + "" - # now do all the substitutions which the LMS module_render normally does, but + # Now do all the substitutions which the LMS module_render normally does, but # we need to do here explicitly since we can get called for our HTML via AJAX html = self.system.replace_urls(html) if self.system.replace_course_urls: @@ -601,12 +608,14 @@ def handle_ajax(self, dispatch, data): 'ungraded_response': self.handle_ungraded_response } - generic_error_message = ( + _ = self.runtime.service(self, "i18n").ugettext + + generic_error_message = _( "We're sorry, there was an error with processing your request. " "Please try reloading your page and trying again." ) - not_found_error_message = ( + not_found_error_message = _( "The state of this problem has changed since you loaded this page. " "Please refresh your page." ) @@ -918,17 +927,19 @@ def check_problem(self, data): answers = self.make_dict_of_responses(data) event_info['answers'] = convert_files_to_filenames(answers) + _ = self.runtime.service(self, "i18n").ugettext + # Too late. Cannot submit if self.closed(): event_info['failure'] = 'closed' self.system.track_function('problem_check_fail', event_info) - raise NotFoundError('Problem is closed') + raise NotFoundError(_("Problem is closed.")) # Problem submitted. Student should reset before checking again if self.done and self.rerandomize == "always": event_info['failure'] = 'unreset' self.system.track_function('problem_check_fail', event_info) - raise NotFoundError('Problem must be reset before it can be checked again') + raise NotFoundError(_("Problem must be reset before it can be checked again.")) # Problem queued. Students must wait a specified waittime before they are allowed to submit if self.lcp.is_queued(): @@ -936,7 +947,7 @@ def check_problem(self, data): prev_submit_time = self.lcp.get_recentmost_queuetime() waittime_between_requests = self.system.xqueue['waittime'] if (current_time - prev_submit_time).total_seconds() < waittime_between_requests: - msg = u'You must wait at least {wait} seconds between submissions'.format( + msg = _(u"You must wait at least {wait} seconds between submissions").format( wait=waittime_between_requests) return {'success': msg, 'html': ''} # Prompts a modal dialog in ajax callback @@ -962,7 +973,8 @@ def check_problem(self, data): # Otherwise, display just an error message, # without a stack trace else: - msg = u"Error: {msg}".format(msg=inst.message) + # Translators: {msg} will be replaced with a problem's error message. + msg = _(u"Error: {msg}").format(msg=inst.message) return {'success': msg} @@ -999,9 +1011,10 @@ def check_problem(self, data): # render problem into HTML html = self.get_problem_html(encapsulate=False) - return {'success': success, - 'contents': html, - } + return { + 'success': success, + 'contents': html, + } def rescore_problem(self): """ @@ -1021,15 +1034,18 @@ def rescore_problem(self): """ event_info = {'state': self.lcp.get_state(), 'problem_id': self.location.url()} + _ = self.runtime.service(self, "i18n").ugettext + if not self.lcp.supports_rescoring(): event_info['failure'] = 'unsupported' self.system.track_function('problem_rescore_fail', event_info) - raise NotImplementedError("Problem's definition does not support rescoring") + # Translators: 'rescoring' refers to the act of re-submitting a student's solution so it can get a new score. + raise NotImplementedError(_("Problem's definition does not support rescoring.")) if not self.done: event_info['failure'] = 'unanswered' self.system.track_function('problem_rescore_fail', event_info) - raise NotFoundError('Problem must be answered before it can be graded again') + raise NotFoundError(_("Problem must be answered before it can be graded again.")) # get old score, for comparison: orig_score = self.lcp.get_score() @@ -1095,32 +1111,39 @@ def save_problem(self, data): answers = self.make_dict_of_responses(data) event_info['answers'] = answers + _ = self.runtime.service(self, "i18n").ugettext # Too late. Cannot submit if self.closed() and not self.max_attempts == 0: event_info['failure'] = 'closed' self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'msg': "Problem is closed"} + return { + 'success': False, + 'msg': _("Problem is closed.") + } # Problem submitted. Student should reset before saving # again. if self.done and self.rerandomize == "always": event_info['failure'] = 'done' self.system.track_function('save_problem_fail', event_info) - return {'success': False, - 'msg': "Problem needs to be reset prior to save"} + return { + 'success': False, + 'msg': _("Problem needs to be reset prior to save.") + } self.lcp.student_answers = answers self.set_state_from_lcp() self.system.track_function('save_problem_success', event_info) - msg = "Your answers have been saved" + msg = _("Your answers have been saved.") if not self.max_attempts == 0: - msg += " but not graded. Hit 'Check' to grade them." - return {'success': True, - 'msg': msg} + msg = _("Your answers have been saved but not graded. Click 'Check' to grade them.") + return { + 'success': True, + 'msg': msg + } def reset_problem(self, _data): """ @@ -1137,18 +1160,23 @@ def reset_problem(self, _data): event_info = dict() event_info['old_state'] = self.lcp.get_state() event_info['problem_id'] = self.location.url() + _ = self.runtime.service(self, "i18n").ugettext if self.closed(): event_info['failure'] = 'closed' self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Problem is closed"} + return { + 'success': False, + 'error': _("Problem is closed.") + } if not self.done: event_info['failure'] = 'not_done' self.system.track_function('reset_problem_fail', event_info) - return {'success': False, - 'error': "Refresh the page and make an attempt before resetting."} + return { + 'success': False, + 'error': _("Refresh the page and make an attempt before resetting.") + } if self.rerandomize in ["always", "onreset"]: # Reset random number generator seed. @@ -1163,8 +1191,10 @@ def reset_problem(self, _data): event_info['new_state'] = self.lcp.get_state() self.system.track_function('reset_problem', event_info) - return {'success': True, - 'html': self.get_problem_html(encapsulate=False)} + return { + 'success': True, + 'html': self.get_problem_html(encapsulate=False) + } class CapaDescriptor(CapaFields, RawDescriptor):