Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 57 additions & 55 deletions common/lib/capa/capa/capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -108,9 +108,9 @@ def __init__( # pylint: disable=invalid-na


class LoncapaProblem(object):
'''
"""
Main class for capa Problems.
'''
"""

def __init__(self, problem_text, id, capa_system, state=None, seed=None):
"""
Expand Down Expand Up @@ -181,9 +181,9 @@ def __init__(self, problem_text, id, capa_system, state=None, seed=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
Expand All @@ -203,11 +203,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,
Expand All @@ -216,9 +216,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()
Expand All @@ -235,7 +235,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:
Expand All @@ -246,12 +246,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():
Expand All @@ -263,30 +263,30 @@ 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
if hasattr(the_input, 'ungraded_response'):
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

Expand All @@ -304,7 +304,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
Expand All @@ -313,7 +313,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)
Expand Down Expand Up @@ -363,7 +363,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
Expand All @@ -372,7 +371,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.capa_system.i18n.ugettext
raise Exception(_("Cannot rescore problems with possible file submissions"))

# use 'student_answers' only if it is provided, and if it might contain a file
# submission that would not exist in the persisted "student_answers".
Expand Down Expand Up @@ -404,7 +404,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):
Expand All @@ -420,35 +420,35 @@ 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']
if self.inputs[input_id]:
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 <include file="foo"> 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')
Expand All @@ -458,14 +458,12 @@ def _process_includes(self):
ifp = self.capa_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.capa_system.filestore
)
'Cannot find file %s in %s', filename, self.capa_system.filestore
)
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
Expand All @@ -478,11 +476,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.capa_system.DEBUG:
Expand Down Expand Up @@ -522,7 +520,7 @@ def _extract_system_path(self, script):
# Check that we are within the filestore tree.
reldir = os.path.relpath(dir, self.capa_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)
Expand All @@ -531,13 +529,13 @@ def _extract_system_path(self, script):
return path

def _extract_context(self, tree):
'''
"""
Extract content of <script>...</script> from the problem.xml file, and exec it in the
context of this problem. Provides ability to randomize problems, and also set
variables for problem answer checking.

Problem XML goes to Python execution context. Runs everything in script tags.
'''
"""
context = {}
context['seed'] = self.seed
all_code = ''
Expand Down Expand Up @@ -584,15 +582,15 @@ 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.

Returns Element tree of XHTML representation of problemtree.
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.
Expand Down Expand Up @@ -632,13 +630,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
Expand Down Expand Up @@ -678,7 +680,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
Expand All @@ -687,7 +689,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(responsetypes.registry.registered_tags())):
Expand Down
16 changes: 8 additions & 8 deletions common/lib/capa/capa/correctmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__()

Expand Down Expand Up @@ -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())
Expand Down
Loading