Merge branch 'feature/vik/fix-oe-storage' into feature/vik/storage-model-fixes
Conflicts: common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py common/lib/xmodule/xmodule/peer_grading_module.py
This commit is contained in:
@@ -12,12 +12,16 @@ from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import Comb
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
|
||||
V1_ATTRIBUTES = ["display_name", "current_task_number", "task_states", "state",
|
||||
"attempts", "ready_to_reset", "max_attempts", "is_graded", "accept_file_upload",
|
||||
"skip_spelling_checks", "due", "graceperiod", "max_score", "data"]
|
||||
V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload",
|
||||
"skip_spelling_checks", "due", "graceperiod", "max_score"]
|
||||
|
||||
V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
|
||||
"student_attempts", "ready_to_reset"]
|
||||
|
||||
V1_ATTRIBUTES = V1_SETTINGS_ATTRIBUTES + V1_STUDENT_ATTRIBUTES
|
||||
|
||||
VERSION_TUPLES = (
|
||||
('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_ATTRIBUTES),
|
||||
('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_SETTINGS_ATTRIBUTES, V1_STUDENT_ATTRIBUTES),
|
||||
)
|
||||
|
||||
DEFAULT_VERSION = 1
|
||||
@@ -56,13 +60,13 @@ class CombinedOpenEndedModule(XModule):
|
||||
|
||||
icon_class = 'problem'
|
||||
|
||||
display_name = String(help="Display name for this module", scope=Scope.settings)
|
||||
display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings)
|
||||
current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.student_state)
|
||||
task_states = String(help="State dictionaries of each task within this module.", default=json.dumps("[]"), scope=Scope.student_state)
|
||||
task_states = Object(help="State dictionaries of each task within this module.", default=[], scope=Scope.student_state)
|
||||
state = String(help="Which step within the current task that the student is on.", default="initial", scope=Scope.student_state)
|
||||
attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state)
|
||||
student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state)
|
||||
ready_to_reset = Boolean(help="If the problem is ready to be reset or not.", default=False, scope=Scope.student_state)
|
||||
max_attempts = Integer(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings)
|
||||
attempts = Integer(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings)
|
||||
is_graded = Boolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
|
||||
accept_file_upload = Boolean(help="Whether or not the problem accepts file uploads.", default=False, scope=Scope.settings)
|
||||
skip_spelling_checks = Boolean(help="Whether or not to skip initial spelling checks.", default=True, scope=Scope.settings)
|
||||
@@ -124,7 +128,8 @@ class CombinedOpenEndedModule(XModule):
|
||||
versions = [i[0] for i in VERSION_TUPLES]
|
||||
descriptors = [i[1] for i in VERSION_TUPLES]
|
||||
modules = [i[2] for i in VERSION_TUPLES]
|
||||
attributes = [i[3] for i in VERSION_TUPLES]
|
||||
settings_attributes = [i[3] for i in VERSION_TUPLES]
|
||||
student_attributes = [i[4] for i in VERSION_TUPLES]
|
||||
|
||||
try:
|
||||
version_index = versions.index(self.version)
|
||||
@@ -134,21 +139,30 @@ class CombinedOpenEndedModule(XModule):
|
||||
self.version = DEFAULT_VERSION
|
||||
version_index = versions.index(self.version)
|
||||
|
||||
self.student_attributes = student_attributes[version_index]
|
||||
self.settings_attributes = settings_attributes[version_index]
|
||||
|
||||
attributes = self.student_attributes + self.settings_attributes
|
||||
|
||||
static_data = {
|
||||
'rewrite_content_links' : self.rewrite_content_links,
|
||||
}
|
||||
|
||||
instance_state = { k: self.__dict__[k] for k in self.__dict__ if k in attributes[version_index]}
|
||||
instance_state = { k: getattr(self,k) for k in attributes}
|
||||
self.child_descriptor = descriptors[version_index](self.system)
|
||||
self.child_definition = descriptors[version_index].definition_from_xml(etree.fromstring(self.data), self.system)
|
||||
self.child_module = modules[version_index](self.system, location, self.child_definition, self.child_descriptor,
|
||||
instance_state = instance_state, static_data= static_data)
|
||||
instance_state = instance_state, static_data= static_data, model_data=model_data, attributes=attributes)
|
||||
|
||||
def get_html(self):
|
||||
return self.child_module.get_html()
|
||||
self.save_instance_data()
|
||||
return_value = self.child_module.get_html()
|
||||
return return_value
|
||||
|
||||
def handle_ajax(self, dispatch, get):
|
||||
return self.child_module.handle_ajax(dispatch, get)
|
||||
self.save_instance_data()
|
||||
return_value = self.child_module.handle_ajax(dispatch, get)
|
||||
self.save_instance_data()
|
||||
return return_value
|
||||
|
||||
def get_instance_state(self):
|
||||
return self.child_module.get_instance_state()
|
||||
@@ -156,8 +170,8 @@ class CombinedOpenEndedModule(XModule):
|
||||
def get_score(self):
|
||||
return self.child_module.get_score()
|
||||
|
||||
def max_score(self):
|
||||
return self.child_module.max_score()
|
||||
#def max_score(self):
|
||||
# return self.child_module.max_score()
|
||||
|
||||
def get_progress(self):
|
||||
return self.child_module.get_progress()
|
||||
@@ -166,9 +180,11 @@ class CombinedOpenEndedModule(XModule):
|
||||
def due_date(self):
|
||||
return self.child_module.due_date
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
return self.child_module.display_name
|
||||
def save_instance_data(self):
|
||||
for attribute in self.student_attributes:
|
||||
child_attr = getattr(self.child_module,attribute)
|
||||
if child_attr != getattr(self, attribute):
|
||||
setattr(self,attribute, getattr(self.child_module,attribute))
|
||||
|
||||
|
||||
class CombinedOpenEndedDescriptor(RawDescriptor):
|
||||
|
||||
@@ -81,7 +81,7 @@ class CombinedOpenEndedV1Module():
|
||||
TEMPLATE_DIR = "combinedopenended"
|
||||
|
||||
def __init__(self, system, location, definition, descriptor,
|
||||
instance_state=None, shared_state=None, metadata = None, static_data = None, **kwargs):
|
||||
instance_state=None, shared_state=None, metadata = None, static_data = None, model_data=None,**kwargs):
|
||||
|
||||
"""
|
||||
Definition file should have one or many task blocks, a rubric block, and a prompt block:
|
||||
@@ -118,6 +118,7 @@ class CombinedOpenEndedV1Module():
|
||||
|
||||
"""
|
||||
|
||||
self._model_data = model_data
|
||||
self.instance_state = instance_state
|
||||
self.display_name = instance_state.get('display_name', "Open Ended")
|
||||
self.rewrite_content_links = static_data.get('rewrite_content_links',"")
|
||||
@@ -133,14 +134,14 @@ class CombinedOpenEndedV1Module():
|
||||
#Overall state of the combined open ended module
|
||||
self.state = instance_state.get('state', self.INITIAL)
|
||||
|
||||
self.attempts = instance_state.get('attempts', 0)
|
||||
self.student_attempts = instance_state.get('student_attempts', 0)
|
||||
|
||||
#Allow reset is true if student has failed the criteria to move to the next child task
|
||||
self.allow_reset = instance_state.get('ready_to_reset', False)
|
||||
self.max_attempts = int(self.instance_state.get('attempts', MAX_ATTEMPTS))
|
||||
self.ready_to_reset = instance_state.get('ready_to_reset', False)
|
||||
self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS)
|
||||
self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT
|
||||
self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
|
||||
self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
|
||||
self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT
|
||||
|
||||
display_due_date_string = self.instance_state.get('due', None)
|
||||
|
||||
@@ -154,7 +155,7 @@ class CombinedOpenEndedV1Module():
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
self._max_score = int(self.instance_state.get('max_score', MAX_SCORE))
|
||||
self._max_score = self.instance_state.get('max_score', MAX_SCORE)
|
||||
|
||||
self.rubric_renderer = CombinedOpenEndedRubric(system, True)
|
||||
rubric_string = stringify_children(definition['rubric'])
|
||||
@@ -163,7 +164,7 @@ class CombinedOpenEndedV1Module():
|
||||
#Static data is passed to the child modules to render
|
||||
self.static_data = {
|
||||
'max_score': self._max_score,
|
||||
'max_attempts': self.max_attempts,
|
||||
'max_attempts': self.attempts,
|
||||
'prompt': definition['prompt'],
|
||||
'rubric': definition['rubric'],
|
||||
'display_name': self.display_name,
|
||||
@@ -197,10 +198,10 @@ class CombinedOpenEndedV1Module():
|
||||
last_response = last_response_data['response']
|
||||
|
||||
loaded_task_state = json.loads(current_task_state)
|
||||
if loaded_task_state['state'] == self.INITIAL:
|
||||
loaded_task_state['state'] = self.ASSESSING
|
||||
loaded_task_state['created'] = True
|
||||
loaded_task_state['history'].append({'answer': last_response})
|
||||
if loaded_task_state['child_state'] == self.INITIAL:
|
||||
loaded_task_state['child_state'] = self.ASSESSING
|
||||
loaded_task_state['child_created'] = True
|
||||
loaded_task_state['child_history'].append({'answer': last_response})
|
||||
current_task_state = json.dumps(loaded_task_state)
|
||||
return current_task_state
|
||||
|
||||
@@ -239,8 +240,8 @@ class CombinedOpenEndedV1Module():
|
||||
self.current_task_xml = self.task_xml[self.current_task_number]
|
||||
|
||||
if self.current_task_number > 0:
|
||||
self.allow_reset = self.check_allow_reset()
|
||||
if self.allow_reset:
|
||||
self.ready_to_reset = self.check_allow_reset()
|
||||
if self.ready_to_reset:
|
||||
self.current_task_number = self.current_task_number - 1
|
||||
|
||||
current_task_type = self.get_tag_name(self.current_task_xml)
|
||||
@@ -265,12 +266,12 @@ class CombinedOpenEndedV1Module():
|
||||
last_response_data = self.get_last_response(self.current_task_number - 1)
|
||||
last_response = last_response_data['response']
|
||||
current_task_state = json.dumps({
|
||||
'state': self.ASSESSING,
|
||||
'child_state': self.ASSESSING,
|
||||
'version': self.STATE_VERSION,
|
||||
'max_score': self._max_score,
|
||||
'attempts': 0,
|
||||
'created': True,
|
||||
'history': [{'answer': last_response}],
|
||||
'child_attempts': 0,
|
||||
'child_created': True,
|
||||
'child_history': [{'answer': last_response}],
|
||||
})
|
||||
self.current_task = child_task_module(self.system, self.location,
|
||||
self.current_task_parsed_xml, self.current_task_descriptor, self.static_data,
|
||||
@@ -293,7 +294,7 @@ class CombinedOpenEndedV1Module():
|
||||
Input: None
|
||||
Output: the allow_reset attribute of the current module.
|
||||
"""
|
||||
if not self.allow_reset:
|
||||
if not self.ready_to_reset:
|
||||
if self.current_task_number > 0:
|
||||
last_response_data = self.get_last_response(self.current_task_number - 1)
|
||||
current_response_data = self.get_current_attributes(self.current_task_number)
|
||||
@@ -301,9 +302,9 @@ class CombinedOpenEndedV1Module():
|
||||
if(current_response_data['min_score_to_attempt'] > last_response_data['score']
|
||||
or current_response_data['max_score_to_attempt'] < last_response_data['score']):
|
||||
self.state = self.DONE
|
||||
self.allow_reset = True
|
||||
self.ready_to_reset = True
|
||||
|
||||
return self.allow_reset
|
||||
return self.ready_to_reset
|
||||
|
||||
def get_context(self):
|
||||
"""
|
||||
@@ -317,7 +318,7 @@ class CombinedOpenEndedV1Module():
|
||||
context = {
|
||||
'items': [{'content': task_html}],
|
||||
'ajax_url': self.system.ajax_url,
|
||||
'allow_reset': self.allow_reset,
|
||||
'allow_reset': self.ready_to_reset,
|
||||
'state': self.state,
|
||||
'task_count': len(self.task_xml),
|
||||
'task_number': self.current_task_number + 1,
|
||||
@@ -395,7 +396,7 @@ class CombinedOpenEndedV1Module():
|
||||
|
||||
task_parsed_xml = task_descriptor.definition_from_xml(etree_xml, self.system)
|
||||
task = children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor,
|
||||
self.static_data, instance_state=task_state)
|
||||
self.static_data, instance_state=task_state, model_data = self._model_data)
|
||||
last_response = task.latest_answer()
|
||||
last_score = task.latest_score()
|
||||
last_post_assessment = task.latest_post_assessment(self.system)
|
||||
@@ -413,7 +414,7 @@ class CombinedOpenEndedV1Module():
|
||||
else:
|
||||
last_post_evaluation = task.format_feedback_with_evaluation(self.system, last_post_assessment)
|
||||
last_post_assessment = last_post_evaluation
|
||||
rubric_data = task._parse_score_msg(task.history[-1].get('post_assessment', ""), self.system)
|
||||
rubric_data = task._parse_score_msg(task.child_history[-1].get('post_assessment', ""), self.system)
|
||||
rubric_scores = rubric_data['rubric_scores']
|
||||
grader_types = rubric_data['grader_types']
|
||||
feedback_items = rubric_data['feedback_items']
|
||||
@@ -427,7 +428,7 @@ class CombinedOpenEndedV1Module():
|
||||
last_post_assessment = ""
|
||||
last_correctness = task.is_last_response_correct()
|
||||
max_score = task.max_score()
|
||||
state = task.state
|
||||
state = task.child_state
|
||||
if task_type in HUMAN_TASK_TYPE:
|
||||
human_task_name = HUMAN_TASK_TYPE[task_type]
|
||||
else:
|
||||
@@ -477,10 +478,10 @@ class CombinedOpenEndedV1Module():
|
||||
Output: boolean indicating whether or not the task state changed.
|
||||
"""
|
||||
changed = False
|
||||
if not self.allow_reset:
|
||||
if not self.ready_to_reset:
|
||||
self.task_states[self.current_task_number] = self.current_task.get_instance_state()
|
||||
current_task_state = json.loads(self.task_states[self.current_task_number])
|
||||
if current_task_state['state'] == self.DONE:
|
||||
if current_task_state['child_state'] == self.DONE:
|
||||
self.current_task_number += 1
|
||||
if self.current_task_number >= (len(self.task_xml)):
|
||||
self.state = self.DONE
|
||||
@@ -626,7 +627,7 @@ class CombinedOpenEndedV1Module():
|
||||
Output: Dictionary to be rendered
|
||||
"""
|
||||
self.update_task_states()
|
||||
return {'success': True, 'html': self.get_html_nonsystem(), 'allow_reset': self.allow_reset}
|
||||
return {'success': True, 'html': self.get_html_nonsystem(), 'allow_reset': self.ready_to_reset}
|
||||
|
||||
def reset(self, get):
|
||||
"""
|
||||
@@ -635,26 +636,26 @@ class CombinedOpenEndedV1Module():
|
||||
Output: AJAX dictionary to tbe rendered
|
||||
"""
|
||||
if self.state != self.DONE:
|
||||
if not self.allow_reset:
|
||||
if not self.ready_to_reset:
|
||||
return self.out_of_sync_error(get)
|
||||
|
||||
if self.attempts > self.max_attempts:
|
||||
if self.student_attempts > self.attempts:
|
||||
return {
|
||||
'success': False,
|
||||
#This is a student_facing_error
|
||||
'error': ('You have attempted this question {0} times. '
|
||||
'You are only allowed to attempt it {1} times.').format(
|
||||
self.attempts, self.max_attempts)
|
||||
self.student_attempts, self.attempts)
|
||||
}
|
||||
self.state = self.INITIAL
|
||||
self.allow_reset = False
|
||||
self.ready_to_reset = False
|
||||
for i in xrange(0, len(self.task_xml)):
|
||||
self.current_task_number = i
|
||||
self.setup_next_task(reset=True)
|
||||
self.current_task.reset(self.system)
|
||||
self.task_states[self.current_task_number] = self.current_task.get_instance_state()
|
||||
self.current_task_number = 0
|
||||
self.allow_reset = False
|
||||
self.ready_to_reset = False
|
||||
self.setup_next_task()
|
||||
return {'success': True, 'html': self.get_html_nonsystem()}
|
||||
|
||||
@@ -670,8 +671,8 @@ class CombinedOpenEndedV1Module():
|
||||
'current_task_number': self.current_task_number,
|
||||
'state': self.state,
|
||||
'task_states': self.task_states,
|
||||
'attempts': self.attempts,
|
||||
'ready_to_reset': self.allow_reset,
|
||||
'student_attempts': self.student_attempts,
|
||||
'ready_to_reset': self.ready_to_reset,
|
||||
}
|
||||
|
||||
return json.dumps(state)
|
||||
@@ -705,7 +706,7 @@ class CombinedOpenEndedV1Module():
|
||||
entirely, in which case they will be in the self.DONE state), and if it is scored or not.
|
||||
@return: Boolean corresponding to the above.
|
||||
"""
|
||||
return (self.state == self.DONE or self.allow_reset) and self.is_scored
|
||||
return (self.state == self.DONE or self.ready_to_reset) and self.is_scored
|
||||
|
||||
def get_score(self):
|
||||
"""
|
||||
|
||||
@@ -98,7 +98,7 @@ class CombinedOpenEndedRubric(object):
|
||||
log.error(error_message)
|
||||
raise RubricParsingError(error_message)
|
||||
|
||||
if total != max_score:
|
||||
if int(total) != int(max_score):
|
||||
#This is a staff_facing_error
|
||||
error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}. Contact the learning sciences group for assistance.".format(
|
||||
max_score, location, total)
|
||||
|
||||
@@ -65,17 +65,17 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
if oeparam is None:
|
||||
#This is a staff_facing_error
|
||||
raise ValueError(error_message.format('oeparam'))
|
||||
if self.prompt is None:
|
||||
if self.child_prompt is None:
|
||||
raise ValueError(error_message.format('prompt'))
|
||||
if self.rubric is None:
|
||||
if self.child_rubric is None:
|
||||
raise ValueError(error_message.format('rubric'))
|
||||
|
||||
self._parse(oeparam, self.prompt, self.rubric, system)
|
||||
self._parse(oeparam, self.child_prompt, self.child_rubric, system)
|
||||
|
||||
if self.created == True and self.state == self.ASSESSING:
|
||||
self.created = False
|
||||
if self.child_created == True and self.child_state == self.ASSESSING:
|
||||
self.child_created = False
|
||||
self.send_to_grader(self.latest_answer(), system)
|
||||
self.created = False
|
||||
self.child_created = False
|
||||
|
||||
|
||||
def _parse(self, oeparam, prompt, rubric, system):
|
||||
@@ -90,8 +90,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
# Note that OpenEndedResponse is agnostic to the specific contents of grader_payload
|
||||
prompt_string = stringify_children(prompt)
|
||||
rubric_string = stringify_children(rubric)
|
||||
self.prompt = prompt_string
|
||||
self.rubric = rubric_string
|
||||
self.child_prompt = prompt_string
|
||||
self.child_rubric = rubric_string
|
||||
|
||||
grader_payload = oeparam.find('grader_payload')
|
||||
grader_payload = grader_payload.text if grader_payload is not None else ''
|
||||
@@ -130,7 +130,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
@param system: ModuleSystem
|
||||
@return: Success indicator
|
||||
"""
|
||||
self.state = self.DONE
|
||||
self.child_state = self.DONE
|
||||
return {'success': True}
|
||||
|
||||
def message_post(self, get, system):
|
||||
@@ -168,7 +168,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
anonymous_student_id = system.anonymous_student_id
|
||||
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
|
||||
anonymous_student_id +
|
||||
str(len(self.history)))
|
||||
str(len(self.child_history)))
|
||||
|
||||
xheader = xqueue_interface.make_xheader(
|
||||
lms_callback_url=system.xqueue['callback_url'],
|
||||
@@ -195,7 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
if error:
|
||||
success = False
|
||||
|
||||
self.state = self.DONE
|
||||
self.child_state = self.DONE
|
||||
|
||||
#This is a student_facing_message
|
||||
return {'success': success, 'msg': "Successfully submitted your feedback."}
|
||||
@@ -219,7 +219,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
# Generate header
|
||||
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
|
||||
anonymous_student_id +
|
||||
str(len(self.history)))
|
||||
str(len(self.child_history)))
|
||||
|
||||
xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'],
|
||||
lms_key=queuekey,
|
||||
@@ -262,7 +262,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
|
||||
self.record_latest_score(new_score_msg['score'])
|
||||
self.record_latest_post_assessment(score_msg)
|
||||
self.state = self.POST_ASSESSMENT
|
||||
self.child_state = self.POST_ASSESSMENT
|
||||
|
||||
return True
|
||||
|
||||
@@ -541,16 +541,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
@param short_feedback: If the long feedback is wanted or not
|
||||
@return: Returns formatted feedback
|
||||
"""
|
||||
if not self.history:
|
||||
if not self.child_history:
|
||||
return ""
|
||||
|
||||
feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', ""), system,
|
||||
feedback_dict = self._parse_score_msg(self.child_history[-1].get('post_assessment', ""), system,
|
||||
join_feedback=join_feedback)
|
||||
if not short_feedback:
|
||||
return feedback_dict['feedback'] if feedback_dict['valid'] else ''
|
||||
if feedback_dict['valid']:
|
||||
short_feedback = self._convert_longform_feedback_to_html(
|
||||
json.loads(self.history[-1].get('post_assessment', "")))
|
||||
json.loads(self.child_history[-1].get('post_assessment', "")))
|
||||
return short_feedback if feedback_dict['valid'] else ''
|
||||
|
||||
def format_feedback_with_evaluation(self, system, feedback):
|
||||
@@ -603,7 +603,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
@param system: Modulesystem (needed to align with other ajax functions)
|
||||
@return: Returns the current state
|
||||
"""
|
||||
state = self.state
|
||||
state = self.child_state
|
||||
return {'state': state}
|
||||
|
||||
def save_answer(self, get, system):
|
||||
@@ -619,7 +619,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
if closed:
|
||||
return msg
|
||||
|
||||
if self.state != self.INITIAL:
|
||||
if self.child_state != self.INITIAL:
|
||||
return self.out_of_sync_error(get)
|
||||
|
||||
# add new history element with answer and empty score and hint.
|
||||
@@ -666,13 +666,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
"""
|
||||
#set context variables and render template
|
||||
eta_string = None
|
||||
if self.state != self.INITIAL:
|
||||
if self.child_state != self.INITIAL:
|
||||
latest = self.latest_answer()
|
||||
previous_answer = latest if latest is not None else self.initial_display
|
||||
post_assessment = self.latest_post_assessment(system)
|
||||
score = self.latest_score()
|
||||
correct = 'correct' if self.is_submission_correct(score) else 'incorrect'
|
||||
if self.state == self.ASSESSING:
|
||||
if self.child_state == self.ASSESSING:
|
||||
eta_string = self.get_eta()
|
||||
else:
|
||||
post_assessment = ""
|
||||
@@ -681,9 +681,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
|
||||
|
||||
context = {
|
||||
'prompt': self.prompt,
|
||||
'prompt': self.child_prompt,
|
||||
'previous_answer': previous_answer,
|
||||
'state': self.state,
|
||||
'state': self.child_state,
|
||||
'allow_reset': self._allow_reset(),
|
||||
'rows': 30,
|
||||
'cols': 80,
|
||||
|
||||
@@ -67,8 +67,12 @@ class OpenEndedChild(object):
|
||||
def __init__(self, system, location, definition, descriptor, static_data,
|
||||
instance_state=None, shared_state=None, **kwargs):
|
||||
# Load instance state
|
||||
|
||||
if instance_state is not None:
|
||||
instance_state = json.loads(instance_state)
|
||||
try:
|
||||
instance_state = json.loads(instance_state)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
instance_state = {}
|
||||
|
||||
@@ -76,26 +80,24 @@ class OpenEndedChild(object):
|
||||
# None for any element, and score and hint can be None for the last (current)
|
||||
# element.
|
||||
# Scores are on scale from 0 to max_score
|
||||
self.history = instance_state.get('history', [])
|
||||
|
||||
self.state = instance_state.get('state', self.INITIAL)
|
||||
self.child_history=instance_state.get('child_history',[])
|
||||
self.child_state=instance_state.get('child_state', self.INITIAL)
|
||||
self.child_created = instance_state.get('child_created', False)
|
||||
self.child_attempts = instance_state.get('child_attempts', 0)
|
||||
|
||||
self.created = instance_state.get('created', False)
|
||||
|
||||
self.attempts = instance_state.get('attempts', 0)
|
||||
self.max_attempts = static_data['max_attempts']
|
||||
|
||||
self.prompt = static_data['prompt']
|
||||
self.rubric = static_data['rubric']
|
||||
self.child_prompt = static_data['prompt']
|
||||
self.child_rubric = static_data['rubric']
|
||||
self.display_name = static_data['display_name']
|
||||
self.accept_file_upload = static_data['accept_file_upload']
|
||||
self.close_date = static_data['close_date']
|
||||
self.s3_interface = static_data['s3_interface']
|
||||
self.skip_basic_checks = static_data['skip_basic_checks']
|
||||
self._max_score = static_data['max_score']
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
self._max_score = static_data['max_score']
|
||||
if system.open_ended_grading_interface:
|
||||
self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
|
||||
self.controller_qs = controller_query_service.ControllerQueryService(system.open_ended_grading_interface,system)
|
||||
@@ -103,8 +105,6 @@ class OpenEndedChild(object):
|
||||
self.peer_gs = MockPeerGradingService()
|
||||
self.controller_qs = None
|
||||
|
||||
|
||||
|
||||
self.system = system
|
||||
|
||||
self.location_string = location
|
||||
@@ -138,32 +138,32 @@ class OpenEndedChild(object):
|
||||
#This is a student_facing_error
|
||||
'error': 'The problem close date has passed, and this problem is now closed.'
|
||||
}
|
||||
elif self.attempts > self.max_attempts:
|
||||
elif self.child_attempts > self.max_attempts:
|
||||
return True, {
|
||||
'success': False,
|
||||
#This is a student_facing_error
|
||||
'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.attempts, self.max_attempts)
|
||||
'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.child_attempts, self.max_attempts)
|
||||
}
|
||||
else:
|
||||
return False, {}
|
||||
|
||||
def latest_answer(self):
|
||||
"""Empty string if not available"""
|
||||
if not self.history:
|
||||
if not self.child_history:
|
||||
return ""
|
||||
return self.history[-1].get('answer', "")
|
||||
return self.child_history[-1].get('answer', "")
|
||||
|
||||
def latest_score(self):
|
||||
"""None if not available"""
|
||||
if not self.history:
|
||||
if not self.child_history:
|
||||
return None
|
||||
return self.history[-1].get('score')
|
||||
return self.child_history[-1].get('score')
|
||||
|
||||
def latest_post_assessment(self, system):
|
||||
"""Empty string if not available"""
|
||||
if not self.history:
|
||||
if not self.child_history:
|
||||
return ""
|
||||
return self.history[-1].get('post_assessment', "")
|
||||
return self.child_history[-1].get('post_assessment', "")
|
||||
|
||||
@staticmethod
|
||||
def sanitize_html(answer):
|
||||
@@ -185,30 +185,30 @@ class OpenEndedChild(object):
|
||||
@return: None
|
||||
"""
|
||||
answer = OpenEndedChild.sanitize_html(answer)
|
||||
self.history.append({'answer': answer})
|
||||
self.child_history.append({'answer': answer})
|
||||
|
||||
def record_latest_score(self, score):
|
||||
"""Assumes that state is right, so we're adding a score to the latest
|
||||
history element"""
|
||||
self.history[-1]['score'] = score
|
||||
self.child_history[-1]['score'] = score
|
||||
|
||||
def record_latest_post_assessment(self, post_assessment):
|
||||
"""Assumes that state is right, so we're adding a score to the latest
|
||||
history element"""
|
||||
self.history[-1]['post_assessment'] = post_assessment
|
||||
self.child_history[-1]['post_assessment'] = post_assessment
|
||||
|
||||
def change_state(self, new_state):
|
||||
"""
|
||||
A centralized place for state changes--allows for hooks. If the
|
||||
current state matches the old state, don't run any hooks.
|
||||
"""
|
||||
if self.state == new_state:
|
||||
if self.child_state == new_state:
|
||||
return
|
||||
|
||||
self.state = new_state
|
||||
self.child_state = new_state
|
||||
|
||||
if self.state == self.DONE:
|
||||
self.attempts += 1
|
||||
if self.child_state == self.DONE:
|
||||
self.child_attempts += 1
|
||||
|
||||
def get_instance_state(self):
|
||||
"""
|
||||
@@ -217,17 +217,17 @@ class OpenEndedChild(object):
|
||||
|
||||
state = {
|
||||
'version': self.STATE_VERSION,
|
||||
'history': self.history,
|
||||
'state': self.state,
|
||||
'child_history': self.child_history,
|
||||
'child_state': self.child_state,
|
||||
'max_score': self._max_score,
|
||||
'attempts': self.attempts,
|
||||
'created': False,
|
||||
'child_attempts': self.child_attempts,
|
||||
'child_created': False,
|
||||
}
|
||||
return json.dumps(state)
|
||||
|
||||
def _allow_reset(self):
|
||||
"""Can the module be reset?"""
|
||||
return (self.state == self.DONE and self.attempts < self.max_attempts)
|
||||
return (self.child_state == self.DONE and self.child_attempts < self.max_attempts)
|
||||
|
||||
def max_score(self):
|
||||
"""
|
||||
@@ -259,10 +259,10 @@ class OpenEndedChild(object):
|
||||
'''
|
||||
if self._max_score > 0:
|
||||
try:
|
||||
return Progress(self.get_score()['score'], self._max_score)
|
||||
return Progress(int(self.get_score()['score']), int(self._max_score))
|
||||
except Exception as err:
|
||||
#This is a dev_facing_error
|
||||
log.exception("Got bad progress from open ended child module. Max Score: {1}".format(self._max_score))
|
||||
log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score))
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -272,7 +272,7 @@ class OpenEndedChild(object):
|
||||
"""
|
||||
#This is a dev_facing_error
|
||||
log.warning("Open ended child state out sync. state: %r, get: %r. %s",
|
||||
self.state, get, msg)
|
||||
self.child_state, get, msg)
|
||||
#This is a student_facing_error
|
||||
return {'success': False,
|
||||
'error': 'The problem state got out-of-sync. Please try reloading the page.'}
|
||||
|
||||
@@ -5,6 +5,7 @@ from lxml import etree
|
||||
from xmodule.capa_module import ComplexEncoder
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.stringify import stringify_children
|
||||
from xblock.core import List, Integer, String, Scope
|
||||
import openendedchild
|
||||
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric
|
||||
@@ -29,8 +30,12 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
</submitmessage>
|
||||
</selfassessment>
|
||||
"""
|
||||
|
||||
TEMPLATE_DIR = "combinedopenended/selfassessment"
|
||||
# states
|
||||
INITIAL = 'initial'
|
||||
ASSESSING = 'assessing'
|
||||
REQUEST_HINT = 'request_hint'
|
||||
DONE = 'done'
|
||||
|
||||
def setup_response(self, system, location, definition, descriptor):
|
||||
"""
|
||||
@@ -41,8 +46,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
@param descriptor: SelfAssessmentDescriptor
|
||||
@return: None
|
||||
"""
|
||||
self.prompt = stringify_children(self.prompt)
|
||||
self.rubric = stringify_children(self.rubric)
|
||||
self.child_prompt = stringify_children(self.child_prompt)
|
||||
self.child_rubric = stringify_children(self.child_rubric)
|
||||
|
||||
def get_html(self, system):
|
||||
"""
|
||||
@@ -51,18 +56,18 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
@return: Rendered HTML
|
||||
"""
|
||||
#set context variables and render template
|
||||
if self.state != self.INITIAL:
|
||||
if self.child_state != self.INITIAL:
|
||||
latest = self.latest_answer()
|
||||
previous_answer = latest if latest is not None else ''
|
||||
else:
|
||||
previous_answer = ''
|
||||
|
||||
context = {
|
||||
'prompt': self.prompt,
|
||||
'prompt': self.child_prompt,
|
||||
'previous_answer': previous_answer,
|
||||
'ajax_url': system.ajax_url,
|
||||
'initial_rubric': self.get_rubric_html(system),
|
||||
'state': self.state,
|
||||
'state': self.child_state,
|
||||
'allow_reset': self._allow_reset(),
|
||||
'child_type': 'selfassessment',
|
||||
'accept_file_upload': self.accept_file_upload,
|
||||
@@ -108,11 +113,11 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
"""
|
||||
Return the appropriate version of the rubric, based on the state.
|
||||
"""
|
||||
if self.state == self.INITIAL:
|
||||
if self.child_state == self.INITIAL:
|
||||
return ''
|
||||
|
||||
rubric_renderer = CombinedOpenEndedRubric(system, False)
|
||||
rubric_dict = rubric_renderer.render_rubric(self.rubric)
|
||||
rubric_dict = rubric_renderer.render_rubric(self.child_rubric)
|
||||
success = rubric_dict['success']
|
||||
rubric_html = rubric_dict['html']
|
||||
|
||||
@@ -121,13 +126,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
'max_score': self._max_score,
|
||||
}
|
||||
|
||||
if self.state == self.ASSESSING:
|
||||
if self.child_state == self.ASSESSING:
|
||||
context['read_only'] = False
|
||||
elif self.state in (self.POST_ASSESSMENT, self.DONE):
|
||||
elif self.child_state in (self.POST_ASSESSMENT, self.DONE):
|
||||
context['read_only'] = True
|
||||
else:
|
||||
#This is a dev_facing_error
|
||||
raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.state))
|
||||
raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.child_state))
|
||||
|
||||
return system.render_template('{0}/self_assessment_rubric.html'.format(self.TEMPLATE_DIR), context)
|
||||
|
||||
@@ -135,10 +140,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
"""
|
||||
Return the appropriate version of the hint view, based on state.
|
||||
"""
|
||||
if self.state in (self.INITIAL, self.ASSESSING):
|
||||
if self.child_state in (self.INITIAL, self.ASSESSING):
|
||||
return ''
|
||||
|
||||
if self.state == self.DONE:
|
||||
if self.child_state == self.DONE:
|
||||
# display the previous hint
|
||||
latest = self.latest_post_assessment(system)
|
||||
hint = latest if latest is not None else ''
|
||||
@@ -147,13 +152,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
|
||||
context = {'hint': hint}
|
||||
|
||||
if self.state == self.POST_ASSESSMENT:
|
||||
if self.child_state == self.POST_ASSESSMENT:
|
||||
context['read_only'] = False
|
||||
elif self.state == self.DONE:
|
||||
elif self.child_state == self.DONE:
|
||||
context['read_only'] = True
|
||||
else:
|
||||
#This is a dev_facing_error
|
||||
raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.state))
|
||||
raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.child_state))
|
||||
|
||||
return system.render_template('{0}/self_assessment_hint.html'.format(self.TEMPLATE_DIR), context)
|
||||
|
||||
@@ -175,7 +180,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
if closed:
|
||||
return msg
|
||||
|
||||
if self.state != self.INITIAL:
|
||||
if self.child_state != self.INITIAL:
|
||||
return self.out_of_sync_error(get)
|
||||
|
||||
error_message = ""
|
||||
@@ -216,7 +221,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
'message_html' only if success is true
|
||||
"""
|
||||
|
||||
if self.state != self.ASSESSING:
|
||||
if self.child_state != self.ASSESSING:
|
||||
return self.out_of_sync_error(get)
|
||||
|
||||
try:
|
||||
@@ -239,7 +244,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
self.change_state(self.DONE)
|
||||
d['allow_reset'] = self._allow_reset()
|
||||
|
||||
d['state'] = self.state
|
||||
d['state'] = self.child_state
|
||||
return d
|
||||
|
||||
def save_hint(self, get, system):
|
||||
@@ -253,7 +258,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
|
||||
with the error key only present if success is False and message_html
|
||||
only if True.
|
||||
'''
|
||||
if self.state != self.POST_ASSESSMENT:
|
||||
if self.child_state != self.POST_ASSESSMENT:
|
||||
# Note: because we only ask for hints on wrong answers, may not have
|
||||
# the same number of hints and answers.
|
||||
return self.out_of_sync_error(get)
|
||||
|
||||
@@ -58,16 +58,16 @@ class PeerGradingModule(XModule):
|
||||
else:
|
||||
self.peer_gs = MockPeerGradingService()
|
||||
|
||||
if self.use_for_single_location == True:
|
||||
if self.use_for_single_location in TRUE_DICT:
|
||||
try:
|
||||
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
|
||||
except:
|
||||
log.error("Linked location {0} for peer grading module {1} does not exist".format(
|
||||
self.link_to_location, self.location))
|
||||
raise
|
||||
due_date = self.linked_problem.metadata.get('peer_grading_due', None)
|
||||
due_date = self.linked_problem._model_data.get('peer_grading_due', None)
|
||||
if due_date:
|
||||
self.metadata['due'] = due_date
|
||||
self._model_data['due'] = due_date
|
||||
|
||||
try:
|
||||
self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string)
|
||||
@@ -120,7 +120,7 @@ class PeerGradingModule(XModule):
|
||||
"""
|
||||
if self.closed():
|
||||
return self.peer_grading_closed()
|
||||
if not self.use_for_single_location:
|
||||
if self.use_for_single_location not in TRUE_DICT:
|
||||
return self.peer_grading()
|
||||
else:
|
||||
return self.peer_grading_problem({'location': self.link_to_location})['html']
|
||||
@@ -171,7 +171,7 @@ class PeerGradingModule(XModule):
|
||||
pass
|
||||
|
||||
def get_score(self):
|
||||
if not self.use_for_single_location or not self.is_graded:
|
||||
if self.use_for_single_location not in TRUE_DICT or self.is_graded not in TRUE_DICT:
|
||||
return None
|
||||
|
||||
try:
|
||||
@@ -204,7 +204,7 @@ class PeerGradingModule(XModule):
|
||||
randomization, and 5/7 on another
|
||||
'''
|
||||
max_grade = None
|
||||
if self.use_for_single_location and self.is_graded:
|
||||
if self.use_for_single_location in TRUE_DICT and self.is_graded in TRUE_DICT:
|
||||
max_grade = self.max_grade
|
||||
return max_grade
|
||||
|
||||
@@ -454,11 +454,13 @@ class PeerGradingModule(XModule):
|
||||
except GradingServiceError:
|
||||
#This is a student_facing_error
|
||||
error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
|
||||
log.error(error_text)
|
||||
success = False
|
||||
# catch error if if the json loads fails
|
||||
except ValueError:
|
||||
#This is a student_facing_error
|
||||
error_text = "Could not get list of problems to peer grade. Please notify course staff."
|
||||
log.error(error_text)
|
||||
success = False
|
||||
except:
|
||||
log.exception("Could not contact peer grading service.")
|
||||
@@ -481,8 +483,8 @@ class PeerGradingModule(XModule):
|
||||
problem_location = problem['location']
|
||||
descriptor = _find_corresponding_module_for_location(problem_location)
|
||||
if descriptor:
|
||||
problem['due'] = descriptor.metadata.get('peer_grading_due', None)
|
||||
grace_period_string = descriptor.metadata.get('graceperiod', None)
|
||||
problem['due'] = descriptor._model_data.get('peer_grading_due', None)
|
||||
grace_period_string = descriptor._model_data.get('graceperiod', None)
|
||||
try:
|
||||
problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
|
||||
except:
|
||||
@@ -517,7 +519,7 @@ class PeerGradingModule(XModule):
|
||||
Show individual problem interface
|
||||
'''
|
||||
if get is None or get.get('location') is None:
|
||||
if not self.use_for_single_location:
|
||||
if self.use_for_single_location not in TRUE_DICT:
|
||||
#This is an error case, because it must be set to use a single location to be called without get parameters
|
||||
#This is a dev_facing_error
|
||||
log.error("Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.")
|
||||
@@ -565,40 +567,3 @@ class PeerGradingDescriptor(RawDescriptor):
|
||||
stores_state = True
|
||||
has_score = True
|
||||
template_dir_name = "peer_grading"
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
|
||||
js_module_name = "HTMLEditingDescriptor"
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
"""
|
||||
Pull out the individual tasks, the rubric, and the prompt, and parse
|
||||
|
||||
Returns:
|
||||
{
|
||||
'rubric': 'some-html',
|
||||
'prompt': 'some-html',
|
||||
'task_xml': dictionary of xml strings,
|
||||
}
|
||||
"""
|
||||
expected_children = []
|
||||
for child in expected_children:
|
||||
if len(xml_object.xpath(child)) == 0:
|
||||
#This is a staff_facing_error
|
||||
raise ValueError("Peer grading definition must include at least one '{0}' tag. Contact the learning sciences group for assistance.".format(child))
|
||||
|
||||
def parse_task(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
return [stringify_children(xml_object.xpath(k)[i]) for i in xrange(0, len(xml_object.xpath(k)))]
|
||||
|
||||
def parse(k):
|
||||
"""Assumes that xml_object has child k"""
|
||||
return xml_object.xpath(k)[0]
|
||||
|
||||
return {}, []
|
||||
|
||||
|
||||
def definition_to_xml(self, resource_fs):
|
||||
'''Return an xml element representing this definition.'''
|
||||
elt = etree.Element('peergrading')
|
||||
return elt
|
||||
|
||||
@@ -127,7 +127,7 @@ class OpenEndedChildTest(unittest.TestCase):
|
||||
def test_reset(self):
|
||||
self.openendedchild.reset(test_system)
|
||||
state = json.loads(self.openendedchild.get_instance_state())
|
||||
self.assertEqual(state['state'], OpenEndedChild.INITIAL)
|
||||
self.assertEqual(state['child_state'], OpenEndedChild.INITIAL)
|
||||
|
||||
|
||||
def test_is_last_response_correct(self):
|
||||
@@ -211,7 +211,7 @@ class OpenEndedModuleTest(unittest.TestCase):
|
||||
self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY)
|
||||
|
||||
state = json.loads(self.openendedmodule.get_instance_state())
|
||||
self.assertIsNotNone(state['state'], OpenEndedModule.DONE)
|
||||
self.assertIsNotNone(state['child_state'], OpenEndedModule.DONE)
|
||||
|
||||
def test_send_to_grader(self):
|
||||
submission = "This is a student submission"
|
||||
@@ -336,7 +336,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
|
||||
descriptor = Mock()
|
||||
|
||||
def setUp(self):
|
||||
self.combinedoe = CombinedOpenEndedV1Module(test_system, self.location, self.definition, self.descriptor, static_data = self.static_data, metadata=self.metadata)
|
||||
self.combinedoe = CombinedOpenEndedV1Module(test_system, self.location, self.definition, self.descriptor, static_data = self.static_data, metadata=self.metadata, instance_state={})
|
||||
|
||||
def test_get_tag_name(self):
|
||||
name = self.combinedoe.get_tag_name("<t>Tag</t>")
|
||||
|
||||
@@ -52,6 +52,7 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
}
|
||||
|
||||
self.module = SelfAssessmentModule(test_system, self.location,
|
||||
self.definition,
|
||||
self.descriptor,
|
||||
static_data)
|
||||
|
||||
@@ -80,18 +81,18 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
self.assertEqual(self.module.get_score()['score'], 0)
|
||||
|
||||
self.module.save_answer({'student_answer': "I am an answer"}, test_system)
|
||||
self.assertEqual(self.module.state, self.module.ASSESSING)
|
||||
self.assertEqual(self.module.child_state, self.module.ASSESSING)
|
||||
|
||||
self.module.save_assessment(mock_query_dict, test_system)
|
||||
self.assertEqual(self.module.state, self.module.DONE)
|
||||
self.assertEqual(self.module.child_state, self.module.DONE)
|
||||
|
||||
|
||||
d = self.module.reset({})
|
||||
self.assertTrue(d['success'])
|
||||
self.assertEqual(self.module.state, self.module.INITIAL)
|
||||
self.assertEqual(self.module.child_state, self.module.INITIAL)
|
||||
|
||||
# if we now assess as right, skip the REQUEST_HINT state
|
||||
self.module.save_answer({'student_answer': 'answer 4'}, test_system)
|
||||
responses['assessment'] = '1'
|
||||
self.module.save_assessment(mock_query_dict, test_system)
|
||||
self.assertEqual(self.module.state, self.module.DONE)
|
||||
self.assertEqual(self.module.child_state, self.module.DONE)
|
||||
|
||||
Reference in New Issue
Block a user