Commit 904ed6c8 by Vik Paruchuri

Combined open ended renders correctly, but internal state tracking broken

parent 697e426c
......@@ -58,7 +58,7 @@ class CombinedOpenEndedModule(XModule):
display_name = String(help="Display name for this module", 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)
ready_to_reset = Boolean(help="If the problem is ready to be reset or not.", default=False, scope=Scope.student_state)
......
......@@ -195,10 +195,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
......@@ -233,9 +233,7 @@ class CombinedOpenEndedV1Module():
current_task_state = None
if len(self.task_states) > self.current_task_number:
current_task_state = self.task_states[self.current_task_number]
model_data = self._model_data['task_states'][self.current_task_number]
log.debug(model_data)
self.current_task_xml = self.task_xml[self.current_task_number]
if self.current_task_number > 0:
......@@ -255,6 +253,7 @@ class CombinedOpenEndedV1Module():
#This sends the etree_xml object through the descriptor module of the current task, and
#returns the xml parsed by the descriptor
log.debug(current_task_state)
self.current_task_parsed_xml = self.current_task_descriptor.definition_from_xml(etree_xml, self.system)
if current_task_state is None and self.current_task_number == 0:
self.current_task = child_task_module(self.system, self.location,
......@@ -268,9 +267,9 @@ class CombinedOpenEndedV1Module():
'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,
......@@ -395,7 +394,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, model_data = self._model_data)
self.static_data, instance_state=self.instance_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 +412,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 +426,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:
......
......@@ -63,17 +63,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):
......@@ -88,8 +88,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 ''
......@@ -128,7 +128,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):
......@@ -166,7 +166,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'],
......@@ -193,7 +193,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."}
......@@ -217,7 +217,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,
......@@ -260,7 +260,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
......@@ -539,16 +539,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):
......@@ -601,7 +601,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):
......@@ -617,7 +617,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.
......@@ -664,13 +664,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 = ""
......@@ -679,9 +679,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, model_data=None, task_number = 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 = {}
......@@ -78,30 +82,37 @@ class OpenEndedChild(object):
# Scores are on scale from 0 to max_score
self._model_data = model_data
task_state = {}
if task_number is not None:
self.task_number = task_number
if instance_state is not None:
task_state =
instance_state['task_states'][task_number]['history']
instance_state['task_states'][task_number]['state']', self.INITIAL)
try:
self.child_history=instance_state['task_states'][task_number]['history']
except:
self.child_history = []
try:
self.child_state=instance_state['task_states'][task_number]['state']
except:
self.child_state = self.INITIAL
self.created = task_state.get('created', False)
try:
self.child_created = instance_state['task_states'][task_number]['created']
except:
self.child_created = False
self.attempts = task_state.get('attempts', 0)
self.max_attempts = static_data['max_attempts']
try:
self.child_attempts = instance_state['task_states'][task_number]['attempts']
except:
self.child_attempts = 0
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)
......@@ -109,8 +120,6 @@ class OpenEndedChild(object):
self.peer_gs = MockPeerGradingService()
self.controller_qs = None
self.system = system
self.location_string = location
......@@ -144,32 +153,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):
......@@ -191,30 +200,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):
"""
......@@ -223,17 +232,17 @@ class OpenEndedChild(object):
state = {
'version': self.STATE_VERSION,
'history': self.history,
'state': self.state,
'history': self.child_history,
'state': self.child_state,
'max_score': self._max_score,
'attempts': self.attempts,
'attempts': self.child_attempts,
'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):
"""
......@@ -278,7 +287,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.'}
......
......@@ -39,22 +39,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
REQUEST_HINT = 'request_hint'
DONE = 'done'
student_answers = List(scope=Scope.student_state, default=[])
scores = List(scope=Scope.student_state, default=[])
hints = List(scope=Scope.student_state, default=[])
state = String(scope=Scope.student_state, default=INITIAL)
# Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect).
max_score = Integer(scope=Scope.settings, default=openendedchild.MAX_SCORE)
max_attempts = Integer(scope=Scope.settings, default=openendedchild.MAX_ATTEMPTS)
attempts = Integer(scope=Scope.student_state, default=0)
rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content)
submitmessage = String(scope=Scope.content)
hintprompt = String(scope=Scope.content)
def setup_response(self, system, location, definition, descriptor):
"""
Sets up the module
......@@ -64,8 +48,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):
"""
......@@ -74,18 +58,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,
......@@ -131,11 +115,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']
......@@ -144,13 +128,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('self_assessment_rubric.html', context)
......@@ -158,10 +142,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 ''
......@@ -170,13 +154,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('self_assessment_hint.html', context)
......@@ -198,7 +182,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 = ""
......@@ -239,7 +223,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:
......@@ -262,7 +246,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):
......@@ -276,7 +260,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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment