tag format.
"""
+
def is_optioninput_valid(optioninput):
"""
Verifies if a given optioninput xml is valid or not.
@@ -284,36 +295,34 @@ def is_optioninput_valid(optioninput):
Returns:
boolean: signifying if the optioninput is valid or not.
"""
- correct_options = [
- option.get('correct').upper() == 'TRUE' for option in optioninput.findall('./option')
- ]
+ correct_options = [option.get("correct").upper() == "TRUE" for option in optioninput.findall("./option")]
return correct_options.count(True) in (0, 1)
- additionals = tree.xpath('//stringresponse/additional_answer')
+ additionals = tree.xpath("//stringresponse/additional_answer")
for additional in additionals:
- answer = additional.get('answer')
+ answer = additional.get("answer")
text = additional.text
if not answer and text: # trigger of old->new conversion
- additional.set('answer', text)
- additional.text = ''
- for optioninput in tree.xpath('//optioninput'):
+ additional.set("answer", text)
+ additional.text = ""
+ for optioninput in tree.xpath("//optioninput"):
if not is_optioninput_valid(optioninput):
raise responsetypes.LoncapaProblemError("Dropdown questions can only have one correct answer.")
correct_option = None
child_options = []
- for option_element in optioninput.findall('./option'):
+ for option_element in optioninput.findall("./option"):
text = option_element.text
- text = text or ''
+ text = text or ""
option_name = text.strip()
- if option_element.get('correct').upper() == 'TRUE':
+ if option_element.get("correct").upper() == "TRUE":
correct_option = option_name
child_options.append("'" + option_name + "'")
if len(child_options) > 0:
- options_string = '(' + ','.join(child_options) + ')'
- optioninput.attrib.update({'options': options_string})
+ options_string = "(" + ",".join(child_options) + ")"
+ optioninput.attrib.update({"options": options_string})
if correct_option:
- optioninput.attrib.update({'correct': correct_option})
+ optioninput.attrib.update({"correct": correct_option})
def do_reset(self):
"""
@@ -332,7 +341,7 @@ def set_initial_display(self):
"""
initial_answers = {}
for responder in self.responders.values():
- if hasattr(responder, 'get_initial_display'):
+ if hasattr(responder, "get_initial_display"):
initial_answers.update(responder.get_initial_display())
self.student_answers = initial_answers
@@ -347,13 +356,15 @@ def get_state(self):
2) Populate any student answers.
"""
- return {'seed': self.seed,
- 'student_answers': self.student_answers,
- 'has_saved_answers': self.has_saved_answers,
- 'correct_map': self.correct_map.get_dict(),
- 'correct_map_history': [cmap.get_dict() for cmap in self.correct_map_history],
- 'input_state': self.input_state,
- 'done': self.done}
+ return {
+ "seed": self.seed,
+ "student_answers": self.student_answers,
+ "has_saved_answers": self.has_saved_answers,
+ "correct_map": self.correct_map.get_dict(),
+ "correct_map_history": [cmap.get_dict() for cmap in self.correct_map_history],
+ "input_state": self.input_state,
+ "done": self.done,
+ }
def get_max_score(self):
"""
@@ -366,11 +377,17 @@ def get_max_score(self):
def calculate_score(self, correct_map=None):
"""
- Compute score for this problem. The score is the number of points awarded.
- Returns a dictionary {'score': integer, from 0 to get_max_score(),
- 'total': get_max_score()}.
+ Compute score for this problem. The score is the number of points awarded.
+
+ Returns::
+
+ dict: {
+ 'score': integer, from 0 to get_max_score(),
+ 'total': get_max_score()
+ }
Takes an optional correctness map for use in the rescore workflow.
+
"""
if correct_map is None:
correct_map = self.correct_map
@@ -379,10 +396,10 @@ def calculate_score(self, correct_map=None):
try:
correct += correct_map.get_npoints(key)
except Exception:
- log.error('key=%s, correct_map = %s', key, correct_map)
+ log.error("key=%s, correct_map = %s", key, correct_map)
raise
- return {'score': correct, 'total': self.get_max_score()}
+ return {"score": correct, "total": self.get_max_score()}
def update_score(self, score_msg, queuekey):
"""
@@ -394,7 +411,7 @@ def update_score(self, score_msg, queuekey):
cmap = CorrectMap()
cmap.update(self.correct_map)
for responder in self.responders.values():
- if hasattr(responder, 'update_score'):
+ if hasattr(responder, "update_score"):
# Each LoncapaResponse will update its specific entries in cmap
# cmap is passed by reference
responder.update_score(score_msg, cmap, queuekey)
@@ -411,7 +428,7 @@ def ungraded_response(self, xqueue_msg, queuekey):
# check against each inputtype
for the_input in self.inputs.values():
# if the input type has an ungraded function, pass in the values
- if hasattr(the_input, 'ungraded_response'):
+ if hasattr(the_input, "ungraded_response"):
the_input.ungraded_response(xqueue_msg, queuekey)
def is_queued(self):
@@ -436,8 +453,7 @@ def get_recentmost_queuetime(self):
if self.correct_map.is_queued(answer_id)
]
queuetimes = [
- datetime.strptime(qt_str, xqueue_interface.dateformat).replace(tzinfo=UTC)
- for qt_str in queuetime_strs
+ datetime.strptime(qt_str, xqueue_interface.dateformat).replace(tzinfo=UTC) for qt_str in queuetime_strs
]
return max(queuetimes)
@@ -478,7 +494,7 @@ def supports_rescoring(self):
that the responsetypes are synchronous. This is convenient as it
permits rescoring to be complete when the rescoring call returns.
"""
- return all('filesubmission' not in responder.allowed_inputfields for responder in self.responders.values())
+ return all("filesubmission" not in responder.allowed_inputfields for responder in self.responders.values())
def get_grade_from_current_answers(self, student_answers, correct_map: Optional[CorrectMap] = None):
"""
@@ -511,7 +527,7 @@ def get_grade_from_current_answers(self, student_answers, correct_map: Optional[
# student_answers contains a proper answer or the filename of
# 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:
+ if "filesubmission" in responder.allowed_inputfields and student_answers is None:
_ = self.capa_system.i18n.gettext
raise Exception(_("Cannot rescore problems with possible file submissions"))
@@ -520,8 +536,7 @@ def get_grade_from_current_answers(self, student_answers, correct_map: Optional[
# If grading method is enabled, we need to pass each student answers and the
# correct map in the history fields.
if (
- "filesubmission" in responder.allowed_inputfields
- and student_answers is not None
+ "filesubmission" in responder.allowed_inputfields and student_answers is not None
) or self.is_grading_method_enabled:
results = responder.evaluate_answers(student_answers, oldcmap)
else:
@@ -545,11 +560,11 @@ def get_question_answers(self):
# include solutions from ... stanzas
for entry in self.tree.xpath("//" + "|//".join(solution_tags)):
- answer = etree.tostring(entry).decode('utf-8')
+ answer = etree.tostring(entry).decode("utf-8")
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
def get_answer_ids(self):
@@ -564,7 +579,7 @@ def get_answer_ids(self):
answer_ids.append(list(results.keys()))
return answer_ids
- def find_correct_answer_text(self, answer_id):
+ def find_correct_answer_text(self, answer_id): # pylint: disable=inconsistent-return-statements
"""
Returns the correct answer(s) for the provided answer_id as a single string.
@@ -578,12 +593,12 @@ def find_correct_answer_text(self, answer_id):
if not xml_elements:
return
xml_element = xml_elements[0]
- answer_text = xml_element.xpath('@answer')
+ answer_text = xml_element.xpath("@answer")
if answer_text:
return answer_id[0]
- if xml_element.tag == 'optioninput':
- return xml_element.xpath('@correct')[0]
- return ', '.join(xml_element.xpath('*[@correct="true"]/text()'))
+ if xml_element.tag == "optioninput":
+ return xml_element.xpath("@correct")[0]
+ return ", ".join(xml_element.xpath('*[@correct="true"]/text()'))
def find_question_label(self, answer_id):
"""
@@ -611,13 +626,13 @@ def generate_default_question_label():
which is used in question number 1 (see example XML in comment above)
There's no question 0 (question IDs start at 1, answer IDs at 2)
"""
- question_nr = int(answer_id.split('_')[-2]) - 1
+ question_nr = int(answer_id.split("_")[-2]) - 1
return _("Question {}").format(question_nr)
_ = self.capa_system.i18n.gettext
# Some questions define a prompt with this format: >>This is a prompt<<
try:
- prompt = self.problem_data[answer_id].get('label')
+ prompt = self.problem_data[answer_id].get("label")
except KeyError:
prompt = None
@@ -652,8 +667,8 @@ def generate_default_question_label():
# then from the first optionresponse we'll end with the .
# If we start in the second optionresponse, we'll find another response in the way,
# stop early, and instead of a question we'll report "Question 2".
- SKIP_ELEMS = ['description']
- LABEL_ELEMS = ['p', 'label']
+ SKIP_ELEMS = ["description"]
+ LABEL_ELEMS = ["p", "label"]
while questiontext_elem is not None and questiontext_elem.tag in SKIP_ELEMS:
questiontext_elem = questiontext_elem.getprevious()
@@ -689,17 +704,16 @@ def find_answer_text(self, answer_id, current_answer):
"""
if isinstance(current_answer, list):
# Multiple answers. This case happens e.g. in multiple choice problems
- answer_text = ", ".join(
- self.find_answer_text(answer_id, answer) for answer in current_answer
- )
+ answer_text = ", ".join(self.find_answer_text(answer_id, answer) for answer in current_answer)
- elif isinstance(current_answer, str) and current_answer.startswith('choice_'):
+ elif isinstance(current_answer, str) and current_answer.startswith("choice_"):
# Many problem (e.g. checkbox) report "choice_0" "choice_1" etc.
# Here we transform it
- elems = self.tree.xpath('//*[@id="{answer_id}"]//*[@name="{choice_number}"]'.format(
- answer_id=answer_id,
- choice_number=current_answer
- ))
+ elems = self.tree.xpath(
+ '//*[@id="{answer_id}"]//*[@name="{choice_number}"]'.format(
+ answer_id=answer_id, choice_number=current_answer
+ )
+ )
if len(elems) == 0:
log.warning("Answer Text Missing for answer id: %s and choice number: %s", answer_id, current_answer)
answer_text = "Answer Text Missing"
@@ -729,51 +743,51 @@ def do_targeted_feedback(self, tree):
"""
_ = self.capa_system.i18n.gettext
# Note that the modifications has been done, avoiding problems if called twice.
- if hasattr(self, 'has_targeted'):
+ if hasattr(self, "has_targeted"):
return
self.has_targeted = True # pylint: disable=attribute-defined-outside-init
- for mult_choice_response in tree.xpath('//multiplechoiceresponse[@targeted-feedback]'):
- show_explanation = mult_choice_response.get('targeted-feedback') == 'alwaysShowCorrectChoiceExplanation'
+ for mult_choice_response in tree.xpath("//multiplechoiceresponse[@targeted-feedback]"):
+ show_explanation = mult_choice_response.get("targeted-feedback") == "alwaysShowCorrectChoiceExplanation"
# Grab the first choicegroup (there should only be one within each tag)
choicegroup = mult_choice_response.xpath('./choicegroup[@type="MultipleChoice"]')[0]
- choices_list = list(choicegroup.iter('choice'))
+ choices_list = list(choicegroup.iter("choice"))
# Find the student answer key that matches our id
- student_answer = self.student_answers.get(choicegroup.get('id'))
+ student_answer = self.student_answers.get(choicegroup.get("id"))
expl_id_for_student_answer = None
# Keep track of the explanation-id that corresponds to the student's answer
# Also, keep track of the solution-id
solution_id = None
- choice_correctness_for_student_answer = _('Incorrect')
+ choice_correctness_for_student_answer = _("Incorrect")
for choice in choices_list:
- if choice.get('name') == student_answer:
- expl_id_for_student_answer = choice.get('explanation-id')
- if choice.get('correct') == 'true':
- choice_correctness_for_student_answer = _('Correct')
- if choice.get('correct') == 'true':
- solution_id = choice.get('explanation-id')
+ if choice.get("name") == student_answer:
+ expl_id_for_student_answer = choice.get("explanation-id")
+ if choice.get("correct") == "true":
+ choice_correctness_for_student_answer = _("Correct")
+ if choice.get("correct") == "true":
+ solution_id = choice.get("explanation-id")
# Filter out targetedfeedback that doesn't correspond to the answer the student selected
# Note: following-sibling will grab all following siblings, so we just want the first in the list
- targetedfeedbackset = mult_choice_response.xpath('./following-sibling::targetedfeedbackset')
+ targetedfeedbackset = mult_choice_response.xpath("./following-sibling::targetedfeedbackset")
if len(targetedfeedbackset) != 0:
targetedfeedbackset = targetedfeedbackset[0]
- targetedfeedbacks = targetedfeedbackset.xpath('./targetedfeedback')
+ targetedfeedbacks = targetedfeedbackset.xpath("./targetedfeedback")
# find the legend by id in choicegroup.html for aria-describedby
- problem_legend_id = str(choicegroup.get('id')) + '-legend'
+ problem_legend_id = str(choicegroup.get("id")) + "-legend"
for targetedfeedback in targetedfeedbacks:
screenreadertext = etree.Element("span")
targetedfeedback.insert(0, screenreadertext)
- screenreadertext.set('class', 'sr')
+ screenreadertext.set("class", "sr")
screenreadertext.text = choice_correctness_for_student_answer
- targetedfeedback.set('role', 'group')
- targetedfeedback.set('aria-describedby', problem_legend_id)
+ targetedfeedback.set("role", "group")
+ targetedfeedback.set("aria-describedby", problem_legend_id)
# Don't show targeted feedback if the student hasn't answer the problem
# or if the target feedback doesn't match the student's (incorrect) answer
- if not self.done or targetedfeedback.get('explanation-id') != expl_id_for_student_answer:
+ if not self.done or targetedfeedback.get("explanation-id") != expl_id_for_student_answer:
targetedfeedbackset.remove(targetedfeedback)
# Do not displace the solution under these circumstances
@@ -784,12 +798,12 @@ def do_targeted_feedback(self, tree):
next_element = targetedfeedbackset.getnext()
parent_element = tree
solution_element = None
- if next_element is not None and next_element.tag == 'solution':
+ if next_element is not None and next_element.tag == "solution":
solution_element = next_element
- elif next_element is not None and next_element.tag == 'solutionset':
- solutions = next_element.xpath('./solution')
+ elif next_element is not None and next_element.tag == "solutionset":
+ solutions = next_element.xpath("./solution")
for solution in solutions:
- if solution.get('explanation-id') == solution_id:
+ if solution.get("explanation-id") == solution_id:
parent_element = next_element
solution_element = solution
@@ -803,7 +817,7 @@ def do_targeted_feedback(self, tree):
parent_element.remove(solution_element)
# Add our solution instead to the targetedfeedbackset and change its tag name
- solution_element.tag = 'targetedfeedback'
+ solution_element.tag = "targetedfeedback"
targetedfeedbackset.append(solution_element)
@@ -812,10 +826,7 @@ def get_html(self):
Main method called externally to get the HTML to be rendered for this capa Problem.
"""
self.do_targeted_feedback(self.tree)
- html = contextualize_text(
- etree.tostring(self._extract_html(self.tree)).decode('utf-8'),
- self.context
- )
+ html = contextualize_text(etree.tostring(self._extract_html(self.tree)).decode("utf-8"), self.context)
return html
def handle_input_ajax(self, data):
@@ -826,9 +837,9 @@ def handle_input_ajax(self, data):
"""
# pull out the id
- input_id = data['input_id']
+ input_id = data["input_id"]
if self.inputs[input_id]:
- dispatch = data['dispatch']
+ 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)
@@ -841,22 +852,16 @@ 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')
+ includes = self.tree.findall(".//include")
for inc in includes:
- filename = inc.get('file')
+ filename = inc.get("file")
if filename is not None:
try:
# open using LoncapaSystem OSFS filesystem
ifp = self.capa_system.resources_fs.open(filename)
except Exception as err: # lint-amnesty, pylint: disable=broad-except
- log.warning(
- 'Error %s in problem xml include: %s',
- err,
- etree.tostring(inc, pretty_print=True)
- )
- log.warning(
- 'Cannot find file %s in %s', filename, self.capa_system.resources_fs
- )
+ log.warning("Error %s in problem xml include: %s", err, etree.tostring(inc, pretty_print=True))
+ log.warning("Cannot find file %s in %s", filename, self.capa_system.resources_fs)
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
if not self.capa_system.DEBUG: # lint-amnesty, pylint: disable=no-else-raise
@@ -867,12 +872,8 @@ def _process_includes(self):
# read in and convert to XML
incxml = etree.XML(ifp.read())
except Exception as err: # lint-amnesty, pylint: disable=broad-except
- log.warning(
- 'Error %s in problem xml include: %s',
- err,
- etree.tostring(inc, pretty_print=True)
- )
- log.warning('Cannot parse XML in %s', (filename))
+ log.warning("Error %s in problem xml include: %s", err, etree.tostring(inc, pretty_print=True))
+ log.warning("Cannot parse XML in %s", (filename))
# if debugging, don't fail - just log error
# TODO (vshnayder): same as above
if not self.capa_system.DEBUG: # lint-amnesty, pylint: disable=no-else-raise
@@ -884,7 +885,7 @@ def _process_includes(self):
parent = inc.getparent()
parent.insert(parent.index(inc), incxml)
parent.remove(inc)
- log.debug('Included %s into %s', filename, self.problem_id)
+ log.debug("Included %s into %s", filename, self.problem_id)
def _extract_system_path(self, script):
"""
@@ -895,10 +896,10 @@ def _extract_system_path(self, script):
script : ?? (TODO)
"""
- DEFAULT_PATH = ['code']
+ DEFAULT_PATH = ["code"]
# Separate paths by :, like the system path.
- raw_path = script.get('system_path', '').split(":") + DEFAULT_PATH
+ raw_path = script.get("system_path", "").split(":") + DEFAULT_PATH
# find additional comma-separated modules search path
path = []
@@ -929,20 +930,20 @@ def _extract_context(self, tree):
Problem XML goes to Python execution context. Runs everything in script tags.
"""
context = {}
- context['seed'] = self.seed
- context['anonymous_student_id'] = self.capa_system.anonymous_student_id
- all_code = ''
+ context["seed"] = self.seed
+ context["anonymous_student_id"] = self.capa_system.anonymous_student_id
+ all_code = ""
python_path = []
- for script in tree.findall('.//script'):
+ for script in tree.findall(".//script"):
- stype = script.get('type')
+ stype = script.get("type")
if stype:
- if 'javascript' in stype:
- continue # skip javascript
- if 'perl' in stype:
- continue # skip perl
+ if "javascript" in stype:
+ continue # skip javascript
+ if "perl" in stype:
+ continue # skip perl
# TODO: evaluate only python
for d in self._extract_system_path(script):
@@ -969,24 +970,24 @@ def _extract_context(self, tree):
python_path=python_path,
extra_files=extra_files,
cache=self.capa_system.cache,
- limit_overrides_context=get_course_id_from_capa_block(
- self.capa_block
- ),
+ limit_overrides_context=get_course_id_from_capa_block(self.capa_block),
slug=self.problem_id,
unsafely=self.capa_system.can_execute_unsafe_code(),
)
except Exception as err:
- log.exception("Error while execing script code: " + all_code) # lint-amnesty, pylint: disable=logging-not-lazy
+ log.exception(
+ "Error while execing script code: " + all_code
+ ) # lint-amnesty, pylint: disable=logging-not-lazy
msg = Text("Error while executing script code: %s" % str(err))
raise responsetypes.LoncapaProblemError(msg)
# Store code source in context, along with the Python path needed to run it correctly.
- context['script_code'] = all_code
- context['python_path'] = python_path
- context['extra_files'] = extra_files or None
+ context["script_code"] = all_code
+ context["python_path"] = python_path
+ context["extra_files"] = extra_files or None
return context
- def _extract_html(self, problemtree): # private
+ def _extract_html(self, problemtree): # private # pylint: disable=inconsistent-return-statements
"""
Main (private) function which converts Problem XML tree to HTML.
Calls itself recursively.
@@ -1003,25 +1004,24 @@ def _extract_html(self, problemtree): # private
# other than to examine .tag to see if it's a string. :(
return
- if (problemtree.tag == 'script' and problemtree.get('type')
- and 'javascript' in problemtree.get('type')):
+ if problemtree.tag == "script" and problemtree.get("type") and "javascript" in problemtree.get("type"):
# leave javascript intact.
return deepcopy(problemtree)
if problemtree.tag in html_problem_semantics:
return
- problemid = problemtree.get('id') # my ID
+ problemid = problemtree.get("id") # my ID
if problemtree.tag in inputtypes.registry.registered_tags():
# If this is an inputtype subtree, let it render itself.
response_data = self.problem_data[problemid]
- status = 'unsubmitted'
- msg = ''
- hint = ''
+ status = "unsubmitted"
+ msg = ""
+ hint = ""
hintmode = None
- input_id = problemtree.get('id')
+ input_id = problemtree.get("id")
answervariable = None
if problemid in self.correct_map:
pid = input_id
@@ -1029,7 +1029,7 @@ def _extract_html(self, problemtree): # private
# If we're withholding correctness, don't show adaptive hints either.
# Note that regular, "demand" hints will be shown, if the course author has added them to the problem.
if not self.capa_block.correctness_available():
- status = 'submitted'
+ status = "submitted"
else:
# If the the problem has not been saved since the last submit set the status to the
# current correctness value and set the message as expected. Otherwise we do not want to
@@ -1040,9 +1040,9 @@ def _extract_html(self, problemtree): # private
hint = self.correct_map.get_hint(pid)
hintmode = self.correct_map.get_hintmode(pid)
- answervariable = self.correct_map.get_property(pid, 'answervariable')
+ answervariable = self.correct_map.get_property(pid, "answervariable")
- value = ''
+ value = ""
if self.student_answers and problemid in self.student_answers:
value = self.student_answers[problemid]
@@ -1051,18 +1051,18 @@ def _extract_html(self, problemtree): # private
# do the rendering
state = {
- 'value': value,
- 'status': status,
- 'id': input_id,
- 'input_state': self.input_state[input_id],
- 'answervariable': answervariable,
- 'response_data': response_data,
- 'has_saved_answers': self.has_saved_answers,
- 'feedback': {
- 'message': msg,
- 'hint': hint,
- 'hintmode': hintmode,
- }
+ "value": value,
+ "status": status,
+ "id": input_id,
+ "input_state": self.input_state[input_id],
+ "answervariable": answervariable,
+ "response_data": response_data,
+ "has_saved_answers": self.has_saved_answers,
+ "feedback": {
+ "message": msg,
+ "hint": hint,
+ "hintmode": hintmode,
+ },
}
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
@@ -1073,9 +1073,7 @@ def _extract_html(self, problemtree): # private
# let each Response render itself
if problemtree in self.responders:
overall_msg = self.correct_map.get_overall_message()
- return self.responders[problemtree].render_html(
- self._extract_html, response_msg=overall_msg
- )
+ return self.responders[problemtree].render_html(self._extract_html, response_msg=overall_msg)
# let each custom renderer render itself:
if problemtree.tag in customrender.registry.registered_tags():
@@ -1091,10 +1089,10 @@ def _extract_html(self, problemtree): # private
tree.append(item_xhtml)
if tree.tag in html_transforms:
- tree.tag = html_transforms[problemtree.tag]['tag']
+ tree.tag = html_transforms[problemtree.tag]["tag"]
else:
# copy attributes over if not innocufying
- for (key, value) in problemtree.items():
+ for key, value in problemtree.items():
tree.set(key, value)
tree.text = problemtree.text
@@ -1116,24 +1114,23 @@ def _preprocess_problem(self, tree, minimal_init): # private
response_id = 1
problem_data = {}
self.responders = {}
- for response in tree.xpath('//' + "|//".join(responsetypes.registry.registered_tags())):
+ for response in tree.xpath("//" + "|//".join(responsetypes.registry.registered_tags())):
responsetype_id = self.problem_id + "_" + str(response_id)
# create and save ID for this response
- response.set('id', responsetype_id)
+ response.set("id", responsetype_id)
response_id += 1
answer_id = 1
input_tags = inputtypes.registry.registered_tags()
inputfields = tree.xpath(
- "|".join(['//' + response.tag + '[@id=$id]//' + x for x in input_tags]),
- id=responsetype_id
+ "|".join(["//" + response.tag + "[@id=$id]//" + x for x in input_tags]), id=responsetype_id
)
# assign one answer_id for each input type
for entry in inputfields:
- entry.attrib['response_id'] = str(response_id)
- entry.attrib['answer_id'] = str(answer_id)
- entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
+ entry.attrib["response_id"] = str(response_id)
+ entry.attrib["answer_id"] = str(answer_id)
+ entry.attrib["id"] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
answer_id = answer_id + 1
self.response_a11y_data(response, inputfields, responsetype_id, problem_data)
@@ -1150,20 +1147,21 @@ def _preprocess_problem(self, tree, minimal_init): # private
# get responder answers (do this only once, since there may be a performance cost,
# eg with externalresponse)
self.responder_answers = {}
- for response in self.responders.keys(): # lint-amnesty, pylint: disable=consider-iterating-dictionary
+ for response in self.responders: # pylint: disable=consider-using-dict-items
try:
self.responder_answers[response] = self.responders[response].get_answers()
except:
- log.debug('responder %s failed to properly return get_answers()',
- self.responders[response]) # FIXME
+ log.debug(
+ "responder %s failed to properly return get_answers()", self.responders[response]
+ ) # FIXME
raise
# ... may not be associated with any specific response; give
# IDs for those separately
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
solution_id = 1
- for solution in tree.findall('.//solution'):
- solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id)
+ for solution in tree.findall(".//solution"):
+ solution.attrib["id"] = "%s_solution_%i" % (self.problem_id, solution_id)
solution_id += 1
return problem_data
@@ -1183,61 +1181,61 @@ def response_a11y_data(self, response, inputfields, responsetype_id, problem_dat
return
element_to_be_deleted = None
- label = ''
+ label = ""
if len(inputfields) > 1:
- response.set('multiple_inputtypes', 'true')
- group_label_tag = response.find('label')
- group_description_tags = response.findall('description')
- group_label_tag_id = 'multiinput-group-label-{}'.format(responsetype_id)
- group_label_tag_text = ''
+ response.set("multiple_inputtypes", "true")
+ group_label_tag = response.find("label")
+ group_description_tags = response.findall("description")
+ group_label_tag_id = "multiinput-group-label-{}".format(responsetype_id)
+ group_label_tag_text = ""
if group_label_tag is not None:
- group_label_tag.tag = 'p'
- group_label_tag.set('id', group_label_tag_id)
- group_label_tag.set('class', 'multi-inputs-group-label')
+ group_label_tag.tag = "p"
+ group_label_tag.set("id", group_label_tag_id)
+ group_label_tag.set("class", "multi-inputs-group-label")
group_label_tag_text = stringify_children(group_label_tag)
- response.set('multiinput-group-label-id', group_label_tag_id)
+ response.set("multiinput-group-label-id", group_label_tag_id)
group_description_ids = []
for index, group_description_tag in enumerate(group_description_tags):
- group_description_tag_id = 'multiinput-group-description-{}-{}'.format(responsetype_id, index)
- group_description_tag.tag = 'p'
- group_description_tag.set('id', group_description_tag_id)
- group_description_tag.set('class', 'multi-inputs-group-description question-description')
+ group_description_tag_id = "multiinput-group-description-{}-{}".format(responsetype_id, index)
+ group_description_tag.tag = "p"
+ group_description_tag.set("id", group_description_tag_id)
+ group_description_tag.set("class", "multi-inputs-group-description question-description")
group_description_ids.append(group_description_tag_id)
if group_description_ids:
- response.set('multiinput-group_description_ids', ' '.join(group_description_ids))
+ response.set("multiinput-group_description_ids", " ".join(group_description_ids))
for inputfield in inputfields:
- problem_data[inputfield.get('id')] = {
- 'group_label': group_label_tag_text,
- 'label': HTML(inputfield.attrib.get('label', '')),
- 'descriptions': {}
+ problem_data[inputfield.get("id")] = {
+ "group_label": group_label_tag_text,
+ "label": HTML(inputfield.attrib.get("label", "")),
+ "descriptions": {},
}
else:
# Extract label value from tag or label attribute from inside the responsetype
- responsetype_label_tag = response.find('label')
+ responsetype_label_tag = response.find("label")
if responsetype_label_tag is not None:
label = stringify_children(responsetype_label_tag)
# store tag containing question text to delete
# it later otherwise question will be rendered twice
element_to_be_deleted = responsetype_label_tag
- elif 'label' in inputfields[0].attrib:
+ elif "label" in inputfields[0].attrib:
# in this case we have old problems with label attribute and p tag having question in it
# we will pick the first sibling of responsetype if its a p tag and match the text with
# the label attribute text. if they are equal then we will use this text as question.
# Get first tag before responsetype, this
may contains the question text.
- p_tag = response.xpath('preceding-sibling::*[1][self::p]')
+ p_tag = response.xpath("preceding-sibling::*[1][self::p]")
- if p_tag and p_tag[0].text == inputfields[0].attrib['label']:
+ if p_tag and p_tag[0].text == inputfields[0].attrib["label"]:
label = stringify_children(p_tag[0])
element_to_be_deleted = p_tag[0]
else:
# In this case the problems don't have tag or label attribute inside the responsetype
# so we will get the first preceding label tag w.r.t to this responsetype.
# This will take care of those multi-question problems that are not using --- in their markdown.
- label_tag = response.xpath('preceding-sibling::*[1][self::label]')
+ label_tag = response.xpath("preceding-sibling::*[1][self::label]")
if label_tag:
label = stringify_children(label_tag[0])
element_to_be_deleted = label_tag[0]
@@ -1247,17 +1245,17 @@ def response_a11y_data(self, response, inputfields, responsetype_id, problem_dat
element_to_be_deleted.getparent().remove(element_to_be_deleted)
# Extract descriptions and set unique id on each description tag
- description_tags = response.findall('description')
+ description_tags = response.findall("description")
description_id = 1
descriptions = OrderedDict()
for description in description_tags:
- descriptions[
- "description_%s_%i" % (responsetype_id, description_id)
- ] = HTML(stringify_children(description))
+ descriptions["description_%s_%i" % (responsetype_id, description_id)] = HTML(
+ stringify_children(description)
+ )
response.remove(description)
description_id += 1
- problem_data[inputfields[0].get('id')] = {
- 'label': HTML(label.strip()) if label else '',
- 'descriptions': descriptions
+ problem_data[inputfields[0].get("id")] = {
+ "label": HTML(label.strip()) if label else "",
+ "descriptions": descriptions,
}
diff --git a/xblocks_contrib/problem/capa/checker.py b/xblocks_contrib/problem/capa/checker.py
index ec774230..f00f4f49 100755
--- a/xblocks_contrib/problem/capa/checker.py
+++ b/xblocks_contrib/problem/capa/checker.py
@@ -16,12 +16,12 @@
from xblocks_contrib.problem.capa.capa_problem import LoncapaProblem
logging.basicConfig(format="%(levelname)s %(message)s")
-log = logging.getLogger('capa.checker')
+log = logging.getLogger("capa.checker")
-class DemoSystem(object): # lint-amnesty, pylint: disable=missing-class-docstring
+class DemoSystem: # pylint: disable=missing-class-docstring
def __init__(self):
- self.lookup = TemplateLookup(directories=[path(__file__).dirname() / 'templates'])
+ self.lookup = TemplateLookup(directories=[path(__file__).dirname() / "templates"])
self.DEBUG = True
def render_template(self, template_filename, dictionary):
@@ -32,13 +32,16 @@ def render_template(self, template_filename, dictionary):
def main(): # lint-amnesty, pylint: disable=missing-function-docstring
- parser = argparse.ArgumentParser(description='Check Problem Files')
- parser.add_argument("command", choices=['test', 'show']) # Watch? Render? Open?
- parser.add_argument("files", nargs="+", type=argparse.FileType('r'))
+ parser = argparse.ArgumentParser(description="Check Problem Files")
+ parser.add_argument("command", choices=["test", "show"]) # Watch? Render? Open?
+ parser.add_argument("files", nargs="+", type=argparse.FileType("r"))
parser.add_argument("--seed", required=False, type=int)
- parser.add_argument("--log-level", required=False, default="INFO",
- choices=['info', 'debug', 'warn', 'error',
- 'INFO', 'DEBUG', 'WARN', 'ERROR'])
+ parser.add_argument(
+ "--log-level",
+ required=False,
+ default="INFO",
+ choices=["info", "debug", "warn", "error", "INFO", "DEBUG", "WARN", "ERROR"],
+ )
args = parser.parse_args()
log.setLevel(args.log_level.upper())
@@ -46,18 +49,22 @@ def main(): # lint-amnesty, pylint: disable=missing-function-docstring
system = DemoSystem()
for problem_file in args.files:
- log.info("Opening {0}".format(problem_file.name))
+ log.info("Opening {0}".format(problem_file.name)) # pylint: disable=logging-format-interpolation
try:
- problem = LoncapaProblem(problem_file, "fakeid", seed=args.seed, system=system) # lint-amnesty, pylint: disable=no-value-for-parameter, unexpected-keyword-arg
+ problem = LoncapaProblem(
+ problem_file, "fakeid", seed=args.seed, system=system
+ ) # lint-amnesty, pylint: disable=no-value-for-parameter, unexpected-keyword-arg
except Exception as ex: # lint-amnesty, pylint: disable=broad-except
- log.error("Could not parse file {0}".format(problem_file.name))
+ log.error(
+ "Could not parse file {0}".format(problem_file.name)
+ ) # pylint: disable=logging-format-interpolation
log.exception(ex)
continue
- if args.command == 'test':
+ if args.command == "test":
command_test(problem)
- elif args.command == 'show':
+ elif args.command == "show":
command_show(problem)
problem_file.close()
@@ -80,10 +87,8 @@ def command_test(problem): # lint-amnesty, pylint: disable=missing-function-doc
check_that_suggested_answers_work(problem)
check_that_blanks_fail(problem)
- log_captured_output(sys.stdout,
- "captured stdout from {0}".format(problem))
- log_captured_output(sys.stderr,
- "captured stderr from {0}".format(problem))
+ log_captured_output(sys.stdout, "captured stdout from {0}".format(problem))
+ log_captured_output(sys.stderr, "captured stderr from {0}".format(problem))
except Exception as e: # lint-amnesty, pylint: disable=broad-except
log.exception(e)
finally:
@@ -92,29 +97,32 @@ def command_test(problem): # lint-amnesty, pylint: disable=missing-function-doc
def check_that_blanks_fail(problem):
"""Leaving it blank should never work. Neither should a space."""
- blank_answers = dict((answer_id, "")
- for answer_id in problem.get_question_answers())
+ blank_answers = dict((answer_id, "") for answer_id in problem.get_question_answers())
grading_results = problem.grade_answers(blank_answers)
try:
- assert all(result == 'incorrect' for result in grading_results.values())
+ assert all(result == "incorrect" for result in grading_results.values())
except AssertionError:
- log.error("Blank accepted as correct answer in {0} for {1}"
- .format(problem,
- [answer_id for answer_id, result
- in sorted(grading_results.items())
- if result != 'incorrect']))
+ # pylint: disable=logging-format-interpolation
+ log.error(
+ "Blank accepted as correct answer in {0} for {1}".format(
+ problem, [answer_id for answer_id, result in sorted(grading_results.items()) if result != "incorrect"]
+ )
+ )
def check_that_suggested_answers_work(problem):
- """Split this up so that we're only used for formula/numeric answers.
-
- Examples of where this fails:
- * Displayed answers use units but acceptable ones do not.
- - L1e0.xml
- - Presents itself as UndefinedVariable (when it tries to pass to calc)
- * "a or d" is what's displayed, but only "a" or "d" is accepted, not the
- string "a or d".
- - L1-e00.xml
+ """
+ Split this up so that we're only used for formula/numeric answers.
+
+ Examples of where this fails::
+
+ * Displayed answers use units but acceptable ones do not.
+ - L1e0.xml
+ - Presents itself as UndefinedVariable (when it tries to pass to calc)
+
+ * "a or d" is what's displayed, but only "a" or "d" is accepted, not the
+ string "a or d".
+ - L1-e00.xml
"""
# These are actual answers we get from the responsetypes
real_answers = problem.get_question_answers()
@@ -122,29 +130,32 @@ def check_that_suggested_answers_work(problem):
# all_answers is real_answers + blanks for other answer_ids for which the
# responsetypes can't provide us pre-canned answers (customresponse)
all_answer_ids = problem.get_answer_ids()
- all_answers = dict((answer_id, real_answers.get(answer_id, ""))
- for answer_id in all_answer_ids)
+ all_answers = dict((answer_id, real_answers.get(answer_id, "")) for answer_id in all_answer_ids)
- log.debug("Real answers: {0}".format(real_answers))
+ log.debug("Real answers: {0}".format(real_answers)) # pylint: disable=logging-format-interpolation
if real_answers:
try:
- real_results = dict((answer_id, result) for answer_id, result
- in problem.grade_answers(all_answers).items()
- if answer_id in real_answers)
+ real_results = dict(
+ (answer_id, result)
+ for answer_id, result in problem.grade_answers(all_answers).items()
+ if answer_id in real_answers
+ )
log.debug(real_results)
- assert(all(result == 'correct'
- for answer_id, result in real_results.items()))
+ assert all(result == "correct" for answer_id, result in real_results.items())
except UndefinedVariable as uv_exc:
- log.error("The variable \"{0}\" specified in the ".format(uv_exc) + # lint-amnesty, pylint: disable=logging-not-lazy
- "solution isn't recognized (is it a units measure?).")
+ log.error(
+ 'The variable "{0}" specified in the '.format(uv_exc) # lint-amnesty, pylint: disable=logging-not-lazy
+ + "solution isn't recognized (is it a units measure?)."
+ )
except AssertionError:
- log.error("The following generated answers were not accepted for {0}:"
- .format(problem))
+ # pylint: disable=logging-format-interpolation
+ log.error("The following generated answers were not accepted for {0}:".format(problem))
for question_id, result in sorted(real_results.items()):
- if result != 'correct':
+ if result != "correct":
+ # pylint: disable=logging-format-interpolation
log.error(" {0} = {1}".format(question_id, real_answers[question_id]))
except Exception as ex: # lint-amnesty, pylint: disable=broad-except
- log.error("Uncaught error in {0}".format(problem))
+ log.error("Uncaught error in {0}".format(problem)) # pylint: disable=logging-format-interpolation
log.exception(ex)
@@ -152,9 +163,9 @@ def log_captured_output(output_stream, stream_name): # lint-amnesty, pylint: di
output_stream.seek(0)
output_text = output_stream.read()
if output_text:
- log.info("##### Begin {0} #####\n".format(stream_name) + output_text) # lint-amnesty, pylint: disable=logging-not-lazy
- log.info("##### End {0} #####".format(stream_name))
+ log.info("##### Begin {0} #####\n".format(stream_name) + output_text)
+ log.info("##### End {0} #####".format(stream_name)) # pylint: disable=logging-format-interpolation
-if __name__ == '__main__':
+if __name__ == "__main__":
sys.exit(main())
diff --git a/xblocks_contrib/problem/capa/correctmap.py b/xblocks_contrib/problem/capa/correctmap.py
index 8ab1d890..de598edb 100644
--- a/xblocks_contrib/problem/capa/correctmap.py
+++ b/xblocks_contrib/problem/capa/correctmap.py
@@ -5,7 +5,7 @@
# Used by responsetypes and capa_problem
-class CorrectMap(object):
+class CorrectMap:
"""
Stores map between answer_id and response evaluation result for each question
in a capa problem. The response evaluation result for each answer_id includes
@@ -39,13 +39,13 @@ def __iter__(self):
return self.cmap.__iter__()
# See the documentation for 'set_dict' for the use of kwargs
- def set( # lint-amnesty, pylint: disable=missing-function-docstring
+ def set( # lint-amnesty, pylint: disable=missing-function-docstring,disable=too-many-positional-arguments
self, # lint-amnesty, pylint: disable=unused-argument
answer_id=None,
correctness=None,
npoints=None,
- msg='',
- hint='',
+ msg="",
+ hint="",
hintmode=None,
queuestate=None,
answervariable=None,
@@ -54,13 +54,13 @@ def set( # lint-amnesty, pylint: disable=missing-function-docstring
if answer_id is not None:
self.cmap[answer_id] = {
- 'correctness': correctness,
- 'npoints': npoints,
- 'msg': msg,
- 'hint': hint,
- 'hintmode': hintmode,
- 'queuestate': queuestate,
- 'answervariable': answervariable,
+ "correctness": correctness,
+ "npoints": npoints,
+ "msg": msg,
+ "hint": hint,
+ "hintmode": hintmode,
+ "queuestate": queuestate,
+ "answervariable": answervariable,
}
def __repr__(self):
@@ -111,7 +111,7 @@ def is_correct(self, answer_id):
Returns true if the problem is correct OR partially correct.
"""
if answer_id in self.cmap:
- return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct']
+ return self.cmap[answer_id]["correctness"] in ["correct", "partially-correct"]
return None
def is_partially_correct(self, answer_id):
@@ -120,24 +120,24 @@ def is_partially_correct(self, answer_id):
Returns true if the problem is partially correct.
"""
if answer_id in self.cmap:
- return self.cmap[answer_id]['correctness'] == 'partially-correct'
+ return self.cmap[answer_id]["correctness"] == "partially-correct"
return None
def is_queued(self, answer_id):
- return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None
+ return answer_id in self.cmap and self.cmap[answer_id]["queuestate"] is not None
def is_right_queuekey(self, answer_id, test_key):
- return self.is_queued(answer_id) and self.cmap[answer_id]['queuestate']['key'] == test_key
+ return self.is_queued(answer_id) and self.cmap[answer_id]["queuestate"]["key"] == test_key
def get_queuetime_str(self, answer_id):
- if self.cmap[answer_id]['queuestate']:
- return self.cmap[answer_id]['queuestate']['time']
+ if self.cmap[answer_id]["queuestate"]:
+ return self.cmap[answer_id]["queuestate"]["time"]
else:
return None
def get_npoints(self, answer_id):
"""Return the number of points for an answer, used for partial credit."""
- npoints = self.get_property(answer_id, 'npoints')
+ npoints = self.get_property(answer_id, "npoints")
if npoints is not None:
return npoints
elif self.is_correct(answer_id):
@@ -157,40 +157,40 @@ def get_property(self, answer_id, property, default=None): # lint-amnesty, pyli
return default
def get_correctness(self, answer_id):
- return self.get_property(answer_id, 'correctness')
+ return self.get_property(answer_id, "correctness")
def get_msg(self, answer_id):
- return self.get_property(answer_id, 'msg', '')
+ return self.get_property(answer_id, "msg", "")
def get_hint(self, answer_id):
- return self.get_property(answer_id, 'hint', '')
+ return self.get_property(answer_id, "hint", "")
def get_hintmode(self, answer_id):
- 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):
"""
- - hint : (string) HTML text for hint
- - hintmode : (string) mode for hint display ('always' or 'on_request')
+ - 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)
+ 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)
+ raise Exception("CorrectMap.update called with invalid argument %s" % other_cmap)
self.cmap.update(other_cmap.get_dict())
self.set_overall_message(other_cmap.get_overall_message())
def set_overall_message(self, message_str):
- """ Set a message that applies to the question as a whole,
- rather than to individual inputs. """
+ """Set a message that applies to the question as a whole,
+ rather than to individual inputs."""
self.overall_message = str(message_str) if message_str else ""
def get_overall_message(self):
- """ Retrieve a message that applies to the question as a whole.
- If no message is available, returns the empty string """
+ """Retrieve a message that applies to the question as a whole.
+ If no message is available, returns the empty string"""
return self.overall_message
diff --git a/xblocks_contrib/problem/capa/customrender.py b/xblocks_contrib/problem/capa/customrender.py
index 1aa73a48..95277ad5 100644
--- a/xblocks_contrib/problem/capa/customrender.py
+++ b/xblocks_contrib/problem/capa/customrender.py
@@ -6,10 +6,9 @@
and the xml element.
"""
-
import logging
import re
-import xml.sax.saxutils as saxutils
+from xml.sax import saxutils
from django.utils import html
from lxml import etree
@@ -24,8 +23,8 @@
# -----------------------------------------------------------------------------
-class MathRenderer(object): # lint-amnesty, pylint: disable=missing-class-docstring
- tags = ['math']
+class MathRenderer: # pylint: disable=missing-class-docstring
+ tags = ["math"]
def __init__(self, system, xml):
r"""
@@ -43,13 +42,13 @@ def __init__(self, system, xml):
self.system = system
self.xml = xml
- mathstr = re.sub(r'\$(.*)\$', r'[mathjaxinline]\1[/mathjaxinline]', xml.text)
- mtag = 'mathjax'
- if r'\displaystyle' not in mathstr:
- mtag += 'inline'
+ mathstr = re.sub(r"\$(.*)\$", r"[mathjaxinline]\1[/mathjaxinline]", xml.text)
+ mtag = "mathjax"
+ if r"\displaystyle" not in mathstr:
+ mtag += "inline"
else:
- mathstr = mathstr.replace(r'\displaystyle', '')
- self.mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag)
+ mathstr = mathstr.replace(r"\displaystyle", "")
+ self.mathstr = mathstr.replace("mathjaxinline]", "%s]" % mtag)
def get_html(self):
"""
@@ -57,18 +56,22 @@ def get_html(self):
"""
# TODO: why are there nested html tags here?? Why are there html tags at all, in fact?
# xss-lint: disable=python-interpolate-html
- html = '%s%s' % ( # lint-amnesty, pylint: disable=redefined-outer-name
- self.mathstr, saxutils.escape(self.xml.tail))
+ html = "%s%s" % ( # lint-amnesty, pylint: disable=redefined-outer-name
+ self.mathstr,
+ saxutils.escape(self.xml.tail),
+ )
try:
xhtml = etree.XML(html)
except Exception as err: # lint-amnesty, pylint: disable=broad-except
if self.system.DEBUG:
# xss-lint: disable=python-interpolate-html
msg = '
Error %s
' % (
- str(err).replace('<', '<')) # xss-lint: disable=python-custom-escape
+ str(err).replace("<", "<")
+ ) # xss-lint: disable=python-custom-escape
# xss-lint: disable=python-interpolate-html
- msg += ('
Failed to construct math expression from
%s ' %
- html.replace('<', '<')) # xss-lint: disable=python-custom-escape
+ msg += "
Failed to construct math expression from
%s " % html.replace(
+ "<", "<"
+ ) # xss-lint: disable=python-custom-escape
msg += "
"
log.error(msg)
return etree.XML(msg)
@@ -83,7 +86,7 @@ def get_html(self):
# -----------------------------------------------------------------------------
-class SolutionRenderer(object):
+class SolutionRenderer:
"""
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.
@@ -91,15 +94,18 @@ class SolutionRenderer(object):
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
ajax call.
"""
- tags = ['solution']
+
+ tags = ["solution"]
def __init__(self, system, xml):
self.system = system
- self.id = xml.get('id')
+ self.id = xml.get("id")
def get_html(self):
- context = {'id': self.id}
- html = self.system.render_template("solutionspan.html", context) # lint-amnesty, pylint: disable=redefined-outer-name
+ context = {"id": self.id}
+ html = self.system.render_template(
+ "solutionspan.html", context
+ ) # lint-amnesty, pylint: disable=redefined-outer-name
return etree.XML(html)
@@ -109,12 +115,13 @@ def get_html(self):
# -----------------------------------------------------------------------------
-class TargetedFeedbackRenderer(object):
+class TargetedFeedbackRenderer:
"""
A targeted feedback is just a ... that is used for displaying an
extended piece of feedback to students if they incorrectly answered a question.
"""
- tags = ['targetedfeedback']
+
+ tags = ["targetedfeedback"]
def __init__(self, system, xml):
self.system = system
@@ -126,7 +133,8 @@ def get_html(self):
"""
# xss-lint: disable=python-wrap-html
html_str = ''.format(
- etree.tostring(self.xml, encoding='unicode'))
+ etree.tostring(self.xml, encoding="unicode")
+ )
try:
xhtml = etree.XML(html_str)
@@ -140,7 +148,9 @@ def get_html(self):
Failed to construct targeted feedback from
{html}