Commit 7871fce7 by Vik Paruchuri

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
parents 8a9ba79d b75b0915
...@@ -12,12 +12,16 @@ from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import Comb ...@@ -12,12 +12,16 @@ from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import Comb
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
V1_ATTRIBUTES = ["display_name", "current_task_number", "task_states", "state", V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload",
"attempts", "ready_to_reset", "max_attempts", "is_graded", "accept_file_upload", "skip_spelling_checks", "due", "graceperiod", "max_score"]
"skip_spelling_checks", "due", "graceperiod", "max_score", "data"]
V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state",
"student_attempts", "ready_to_reset"]
V1_ATTRIBUTES = V1_SETTINGS_ATTRIBUTES + V1_STUDENT_ATTRIBUTES
VERSION_TUPLES = ( VERSION_TUPLES = (
('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_ATTRIBUTES), ('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_SETTINGS_ATTRIBUTES, V1_STUDENT_ATTRIBUTES),
) )
DEFAULT_VERSION = 1 DEFAULT_VERSION = 1
...@@ -56,13 +60,13 @@ class CombinedOpenEndedModule(XModule): ...@@ -56,13 +60,13 @@ class CombinedOpenEndedModule(XModule):
icon_class = 'problem' 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) 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) 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) 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) 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) 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) 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): ...@@ -124,7 +128,8 @@ class CombinedOpenEndedModule(XModule):
versions = [i[0] for i in VERSION_TUPLES] versions = [i[0] for i in VERSION_TUPLES]
descriptors = [i[1] for i in VERSION_TUPLES] descriptors = [i[1] for i in VERSION_TUPLES]
modules = [i[2] 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: try:
version_index = versions.index(self.version) version_index = versions.index(self.version)
...@@ -134,21 +139,30 @@ class CombinedOpenEndedModule(XModule): ...@@ -134,21 +139,30 @@ class CombinedOpenEndedModule(XModule):
self.version = DEFAULT_VERSION self.version = DEFAULT_VERSION
version_index = versions.index(self.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 = { static_data = {
'rewrite_content_links' : self.rewrite_content_links, 'rewrite_content_links' : self.rewrite_content_links,
} }
instance_state = { k: getattr(self,k) for k in attributes}
instance_state = { k: self.__dict__[k] for k in self.__dict__ if k in attributes[version_index]}
self.child_descriptor = descriptors[version_index](self.system) 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_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, 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): 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): 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): def get_instance_state(self):
return self.child_module.get_instance_state() return self.child_module.get_instance_state()
...@@ -156,8 +170,8 @@ class CombinedOpenEndedModule(XModule): ...@@ -156,8 +170,8 @@ class CombinedOpenEndedModule(XModule):
def get_score(self): def get_score(self):
return self.child_module.get_score() return self.child_module.get_score()
def max_score(self): #def max_score(self):
return self.child_module.max_score() # return self.child_module.max_score()
def get_progress(self): def get_progress(self):
return self.child_module.get_progress() return self.child_module.get_progress()
...@@ -166,9 +180,11 @@ class CombinedOpenEndedModule(XModule): ...@@ -166,9 +180,11 @@ class CombinedOpenEndedModule(XModule):
def due_date(self): def due_date(self):
return self.child_module.due_date return self.child_module.due_date
@property def save_instance_data(self):
def display_name(self): for attribute in self.student_attributes:
return self.child_module.display_name child_attr = getattr(self.child_module,attribute)
if child_attr != getattr(self, attribute):
setattr(self,attribute, getattr(self.child_module,attribute))
class CombinedOpenEndedDescriptor(RawDescriptor): class CombinedOpenEndedDescriptor(RawDescriptor):
......
...@@ -98,7 +98,7 @@ class CombinedOpenEndedRubric(object): ...@@ -98,7 +98,7 @@ class CombinedOpenEndedRubric(object):
log.error(error_message) log.error(error_message)
raise RubricParsingError(error_message) raise RubricParsingError(error_message)
if total != max_score: if int(total) != int(max_score):
#This is a staff_facing_error #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( 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) max_score, location, total)
......
...@@ -65,17 +65,17 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -65,17 +65,17 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if oeparam is None: if oeparam is None:
#This is a staff_facing_error #This is a staff_facing_error
raise ValueError(error_message.format('oeparam')) raise ValueError(error_message.format('oeparam'))
if self.prompt is None: if self.child_prompt is None:
raise ValueError(error_message.format('prompt')) raise ValueError(error_message.format('prompt'))
if self.rubric is None: if self.child_rubric is None:
raise ValueError(error_message.format('rubric')) 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: if self.child_created == True and self.child_state == self.ASSESSING:
self.created = False self.child_created = False
self.send_to_grader(self.latest_answer(), system) self.send_to_grader(self.latest_answer(), system)
self.created = False self.child_created = False
def _parse(self, oeparam, prompt, rubric, system): def _parse(self, oeparam, prompt, rubric, system):
...@@ -90,8 +90,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -90,8 +90,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# Note that OpenEndedResponse is agnostic to the specific contents of grader_payload # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload
prompt_string = stringify_children(prompt) prompt_string = stringify_children(prompt)
rubric_string = stringify_children(rubric) rubric_string = stringify_children(rubric)
self.prompt = prompt_string self.child_prompt = prompt_string
self.rubric = rubric_string self.child_rubric = rubric_string
grader_payload = oeparam.find('grader_payload') grader_payload = oeparam.find('grader_payload')
grader_payload = grader_payload.text if grader_payload is not None else '' grader_payload = grader_payload.text if grader_payload is not None else ''
...@@ -130,7 +130,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -130,7 +130,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
@param system: ModuleSystem @param system: ModuleSystem
@return: Success indicator @return: Success indicator
""" """
self.state = self.DONE self.child_state = self.DONE
return {'success': True} return {'success': True}
def message_post(self, get, system): def message_post(self, get, system):
...@@ -168,7 +168,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -168,7 +168,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
anonymous_student_id = system.anonymous_student_id anonymous_student_id = system.anonymous_student_id
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
anonymous_student_id + anonymous_student_id +
str(len(self.history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader( xheader = xqueue_interface.make_xheader(
lms_callback_url=system.xqueue['callback_url'], lms_callback_url=system.xqueue['callback_url'],
...@@ -195,7 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -195,7 +195,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if error: if error:
success = False success = False
self.state = self.DONE self.child_state = self.DONE
#This is a student_facing_message #This is a student_facing_message
return {'success': success, 'msg': "Successfully submitted your feedback."} return {'success': success, 'msg': "Successfully submitted your feedback."}
...@@ -219,7 +219,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -219,7 +219,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# Generate header # Generate header
queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime +
anonymous_student_id + anonymous_student_id +
str(len(self.history))) str(len(self.child_history)))
xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'], xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'],
lms_key=queuekey, lms_key=queuekey,
...@@ -262,7 +262,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -262,7 +262,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
self.record_latest_score(new_score_msg['score']) self.record_latest_score(new_score_msg['score'])
self.record_latest_post_assessment(score_msg) self.record_latest_post_assessment(score_msg)
self.state = self.POST_ASSESSMENT self.child_state = self.POST_ASSESSMENT
return True return True
...@@ -541,16 +541,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -541,16 +541,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
@param short_feedback: If the long feedback is wanted or not @param short_feedback: If the long feedback is wanted or not
@return: Returns formatted feedback @return: Returns formatted feedback
""" """
if not self.history: if not self.child_history:
return "" 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) join_feedback=join_feedback)
if not short_feedback: if not short_feedback:
return feedback_dict['feedback'] if feedback_dict['valid'] else '' return feedback_dict['feedback'] if feedback_dict['valid'] else ''
if feedback_dict['valid']: if feedback_dict['valid']:
short_feedback = self._convert_longform_feedback_to_html( 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 '' return short_feedback if feedback_dict['valid'] else ''
def format_feedback_with_evaluation(self, system, feedback): def format_feedback_with_evaluation(self, system, feedback):
...@@ -603,7 +603,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -603,7 +603,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
@param system: Modulesystem (needed to align with other ajax functions) @param system: Modulesystem (needed to align with other ajax functions)
@return: Returns the current state @return: Returns the current state
""" """
state = self.state state = self.child_state
return {'state': state} return {'state': state}
def save_answer(self, get, system): def save_answer(self, get, system):
...@@ -619,7 +619,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -619,7 +619,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
if closed: if closed:
return msg return msg
if self.state != self.INITIAL: if self.child_state != self.INITIAL:
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
# add new history element with answer and empty score and hint. # add new history element with answer and empty score and hint.
...@@ -666,13 +666,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -666,13 +666,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
""" """
#set context variables and render template #set context variables and render template
eta_string = None eta_string = None
if self.state != self.INITIAL: if self.child_state != self.INITIAL:
latest = self.latest_answer() latest = self.latest_answer()
previous_answer = latest if latest is not None else self.initial_display previous_answer = latest if latest is not None else self.initial_display
post_assessment = self.latest_post_assessment(system) post_assessment = self.latest_post_assessment(system)
score = self.latest_score() score = self.latest_score()
correct = 'correct' if self.is_submission_correct(score) else 'incorrect' 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() eta_string = self.get_eta()
else: else:
post_assessment = "" post_assessment = ""
...@@ -681,9 +681,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): ...@@ -681,9 +681,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
context = { context = {
'prompt': self.prompt, 'prompt': self.child_prompt,
'previous_answer': previous_answer, 'previous_answer': previous_answer,
'state': self.state, 'state': self.child_state,
'allow_reset': self._allow_reset(), 'allow_reset': self._allow_reset(),
'rows': 30, 'rows': 30,
'cols': 80, 'cols': 80,
......
...@@ -67,8 +67,12 @@ class OpenEndedChild(object): ...@@ -67,8 +67,12 @@ class OpenEndedChild(object):
def __init__(self, system, location, definition, descriptor, static_data, def __init__(self, system, location, definition, descriptor, static_data,
instance_state=None, shared_state=None, **kwargs): instance_state=None, shared_state=None, **kwargs):
# Load instance state # Load instance state
if instance_state is not None: if instance_state is not None:
instance_state = json.loads(instance_state) try:
instance_state = json.loads(instance_state)
except:
pass
else: else:
instance_state = {} instance_state = {}
...@@ -76,26 +80,24 @@ class OpenEndedChild(object): ...@@ -76,26 +80,24 @@ class OpenEndedChild(object):
# None for any element, and score and hint can be None for the last (current) # None for any element, and score and hint can be None for the last (current)
# element. # element.
# Scores are on scale from 0 to max_score # Scores are on scale from 0 to max_score
self.history = instance_state.get('history', [])
self.state = instance_state.get('state', self.INITIAL)
self.created = instance_state.get('created', False) 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.attempts = instance_state.get('attempts', 0)
self.max_attempts = static_data['max_attempts'] self.max_attempts = static_data['max_attempts']
self.child_prompt = static_data['prompt']
self.prompt = static_data['prompt'] self.child_rubric = static_data['rubric']
self.rubric = static_data['rubric']
self.display_name = static_data['display_name'] self.display_name = static_data['display_name']
self.accept_file_upload = static_data['accept_file_upload'] self.accept_file_upload = static_data['accept_file_upload']
self.close_date = static_data['close_date'] self.close_date = static_data['close_date']
self.s3_interface = static_data['s3_interface'] self.s3_interface = static_data['s3_interface']
self.skip_basic_checks = static_data['skip_basic_checks'] 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 # Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect). # completion (doesn't matter if you self-assessed correct/incorrect).
self._max_score = static_data['max_score']
if system.open_ended_grading_interface: if system.open_ended_grading_interface:
self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system) self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system)
self.controller_qs = controller_query_service.ControllerQueryService(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): ...@@ -103,8 +105,6 @@ class OpenEndedChild(object):
self.peer_gs = MockPeerGradingService() self.peer_gs = MockPeerGradingService()
self.controller_qs = None self.controller_qs = None
self.system = system self.system = system
self.location_string = location self.location_string = location
...@@ -138,32 +138,32 @@ class OpenEndedChild(object): ...@@ -138,32 +138,32 @@ class OpenEndedChild(object):
#This is a student_facing_error #This is a student_facing_error
'error': 'The problem close date has passed, and this problem is now closed.' '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, { return True, {
'success': False, 'success': False,
#This is a student_facing_error #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: else:
return False, {} return False, {}
def latest_answer(self): def latest_answer(self):
"""Empty string if not available""" """Empty string if not available"""
if not self.history: if not self.child_history:
return "" return ""
return self.history[-1].get('answer', "") return self.child_history[-1].get('answer', "")
def latest_score(self): def latest_score(self):
"""None if not available""" """None if not available"""
if not self.history: if not self.child_history:
return None return None
return self.history[-1].get('score') return self.child_history[-1].get('score')
def latest_post_assessment(self, system): def latest_post_assessment(self, system):
"""Empty string if not available""" """Empty string if not available"""
if not self.history: if not self.child_history:
return "" return ""
return self.history[-1].get('post_assessment', "") return self.child_history[-1].get('post_assessment', "")
@staticmethod @staticmethod
def sanitize_html(answer): def sanitize_html(answer):
...@@ -185,30 +185,30 @@ class OpenEndedChild(object): ...@@ -185,30 +185,30 @@ class OpenEndedChild(object):
@return: None @return: None
""" """
answer = OpenEndedChild.sanitize_html(answer) answer = OpenEndedChild.sanitize_html(answer)
self.history.append({'answer': answer}) self.child_history.append({'answer': answer})
def record_latest_score(self, score): def record_latest_score(self, score):
"""Assumes that state is right, so we're adding a score to the latest """Assumes that state is right, so we're adding a score to the latest
history element""" history element"""
self.history[-1]['score'] = score self.child_history[-1]['score'] = score
def record_latest_post_assessment(self, post_assessment): def record_latest_post_assessment(self, post_assessment):
"""Assumes that state is right, so we're adding a score to the latest """Assumes that state is right, so we're adding a score to the latest
history element""" history element"""
self.history[-1]['post_assessment'] = post_assessment self.child_history[-1]['post_assessment'] = post_assessment
def change_state(self, new_state): def change_state(self, new_state):
""" """
A centralized place for state changes--allows for hooks. If the A centralized place for state changes--allows for hooks. If the
current state matches the old state, don't run any hooks. current state matches the old state, don't run any hooks.
""" """
if self.state == new_state: if self.child_state == new_state:
return return
self.state = new_state self.child_state = new_state
if self.state == self.DONE: if self.child_state == self.DONE:
self.attempts += 1 self.child_attempts += 1
def get_instance_state(self): def get_instance_state(self):
""" """
...@@ -217,17 +217,17 @@ class OpenEndedChild(object): ...@@ -217,17 +217,17 @@ class OpenEndedChild(object):
state = { state = {
'version': self.STATE_VERSION, 'version': self.STATE_VERSION,
'history': self.history, 'child_history': self.child_history,
'state': self.state, 'child_state': self.child_state,
'max_score': self._max_score, 'max_score': self._max_score,
'attempts': self.attempts, 'child_attempts': self.child_attempts,
'created': False, 'child_created': False,
} }
return json.dumps(state) return json.dumps(state)
def _allow_reset(self): def _allow_reset(self):
"""Can the module be reset?""" """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): def max_score(self):
""" """
...@@ -259,10 +259,10 @@ class OpenEndedChild(object): ...@@ -259,10 +259,10 @@ class OpenEndedChild(object):
''' '''
if self._max_score > 0: if self._max_score > 0:
try: 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: except Exception as err:
#This is a dev_facing_error #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
return None return None
...@@ -272,7 +272,7 @@ class OpenEndedChild(object): ...@@ -272,7 +272,7 @@ class OpenEndedChild(object):
""" """
#This is a dev_facing_error #This is a dev_facing_error
log.warning("Open ended child state out sync. state: %r, get: %r. %s", 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 #This is a student_facing_error
return {'success': False, return {'success': False,
'error': 'The problem state got out-of-sync. Please try reloading the page.'} 'error': 'The problem state got out-of-sync. Please try reloading the page.'}
......
...@@ -5,6 +5,7 @@ from lxml import etree ...@@ -5,6 +5,7 @@ from lxml import etree
from xmodule.capa_module import ComplexEncoder from xmodule.capa_module import ComplexEncoder
from xmodule.progress import Progress from xmodule.progress import Progress
from xmodule.stringify import stringify_children from xmodule.stringify import stringify_children
from xblock.core import List, Integer, String, Scope
import openendedchild import openendedchild
from combined_open_ended_rubric import CombinedOpenEndedRubric from combined_open_ended_rubric import CombinedOpenEndedRubric
...@@ -29,8 +30,12 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -29,8 +30,12 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
</submitmessage> </submitmessage>
</selfassessment> </selfassessment>
""" """
TEMPLATE_DIR = "combinedopenended/selfassessment" TEMPLATE_DIR = "combinedopenended/selfassessment"
# states
INITIAL = 'initial'
ASSESSING = 'assessing'
REQUEST_HINT = 'request_hint'
DONE = 'done'
def setup_response(self, system, location, definition, descriptor): def setup_response(self, system, location, definition, descriptor):
""" """
...@@ -41,8 +46,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -41,8 +46,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
@param descriptor: SelfAssessmentDescriptor @param descriptor: SelfAssessmentDescriptor
@return: None @return: None
""" """
self.prompt = stringify_children(self.prompt) self.child_prompt = stringify_children(self.child_prompt)
self.rubric = stringify_children(self.rubric) self.child_rubric = stringify_children(self.child_rubric)
def get_html(self, system): def get_html(self, system):
""" """
...@@ -51,18 +56,18 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -51,18 +56,18 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
@return: Rendered HTML @return: Rendered HTML
""" """
#set context variables and render template #set context variables and render template
if self.state != self.INITIAL: if self.child_state != self.INITIAL:
latest = self.latest_answer() latest = self.latest_answer()
previous_answer = latest if latest is not None else '' previous_answer = latest if latest is not None else ''
else: else:
previous_answer = '' previous_answer = ''
context = { context = {
'prompt': self.prompt, 'prompt': self.child_prompt,
'previous_answer': previous_answer, 'previous_answer': previous_answer,
'ajax_url': system.ajax_url, 'ajax_url': system.ajax_url,
'initial_rubric': self.get_rubric_html(system), 'initial_rubric': self.get_rubric_html(system),
'state': self.state, 'state': self.child_state,
'allow_reset': self._allow_reset(), 'allow_reset': self._allow_reset(),
'child_type': 'selfassessment', 'child_type': 'selfassessment',
'accept_file_upload': self.accept_file_upload, 'accept_file_upload': self.accept_file_upload,
...@@ -108,11 +113,11 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -108,11 +113,11 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
""" """
Return the appropriate version of the rubric, based on the state. Return the appropriate version of the rubric, based on the state.
""" """
if self.state == self.INITIAL: if self.child_state == self.INITIAL:
return '' return ''
rubric_renderer = CombinedOpenEndedRubric(system, False) 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'] success = rubric_dict['success']
rubric_html = rubric_dict['html'] rubric_html = rubric_dict['html']
...@@ -121,13 +126,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -121,13 +126,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
'max_score': self._max_score, 'max_score': self._max_score,
} }
if self.state == self.ASSESSING: if self.child_state == self.ASSESSING:
context['read_only'] = False 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 context['read_only'] = True
else: else:
#This is a dev_facing_error #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) return system.render_template('{0}/self_assessment_rubric.html'.format(self.TEMPLATE_DIR), context)
...@@ -135,10 +140,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -135,10 +140,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
""" """
Return the appropriate version of the hint view, based on state. 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 '' return ''
if self.state == self.DONE: if self.child_state == self.DONE:
# display the previous hint # display the previous hint
latest = self.latest_post_assessment(system) latest = self.latest_post_assessment(system)
hint = latest if latest is not None else '' hint = latest if latest is not None else ''
...@@ -147,13 +152,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -147,13 +152,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
context = {'hint': hint} context = {'hint': hint}
if self.state == self.POST_ASSESSMENT: if self.child_state == self.POST_ASSESSMENT:
context['read_only'] = False context['read_only'] = False
elif self.state == self.DONE: elif self.child_state == self.DONE:
context['read_only'] = True context['read_only'] = True
else: else:
#This is a dev_facing_error #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) return system.render_template('{0}/self_assessment_hint.html'.format(self.TEMPLATE_DIR), context)
...@@ -175,7 +180,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -175,7 +180,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
if closed: if closed:
return msg return msg
if self.state != self.INITIAL: if self.child_state != self.INITIAL:
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
error_message = "" error_message = ""
...@@ -216,7 +221,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -216,7 +221,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
'message_html' only if success is true '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) return self.out_of_sync_error(get)
try: try:
...@@ -239,7 +244,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -239,7 +244,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
self.change_state(self.DONE) self.change_state(self.DONE)
d['allow_reset'] = self._allow_reset() d['allow_reset'] = self._allow_reset()
d['state'] = self.state d['state'] = self.child_state
return d return d
def save_hint(self, get, system): def save_hint(self, get, system):
...@@ -253,7 +258,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): ...@@ -253,7 +258,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
with the error key only present if success is False and message_html with the error key only present if success is False and message_html
only if True. 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 # Note: because we only ask for hints on wrong answers, may not have
# the same number of hints and answers. # the same number of hints and answers.
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
......
...@@ -58,16 +58,16 @@ class PeerGradingModule(XModule): ...@@ -58,16 +58,16 @@ class PeerGradingModule(XModule):
else: else:
self.peer_gs = MockPeerGradingService() self.peer_gs = MockPeerGradingService()
if self.use_for_single_location == True: if self.use_for_single_location in TRUE_DICT:
try: try:
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
except: except:
log.error("Linked location {0} for peer grading module {1} does not exist".format( log.error("Linked location {0} for peer grading module {1} does not exist".format(
self.link_to_location, self.location)) self.link_to_location, self.location))
raise 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: if due_date:
self.metadata['due'] = due_date self._model_data['due'] = due_date
try: try:
self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string) self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string)
...@@ -120,7 +120,7 @@ class PeerGradingModule(XModule): ...@@ -120,7 +120,7 @@ class PeerGradingModule(XModule):
""" """
if self.closed(): if self.closed():
return self.peer_grading_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() return self.peer_grading()
else: else:
return self.peer_grading_problem({'location': self.link_to_location})['html'] return self.peer_grading_problem({'location': self.link_to_location})['html']
...@@ -171,7 +171,7 @@ class PeerGradingModule(XModule): ...@@ -171,7 +171,7 @@ class PeerGradingModule(XModule):
pass pass
def get_score(self): 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 return None
try: try:
...@@ -204,7 +204,7 @@ class PeerGradingModule(XModule): ...@@ -204,7 +204,7 @@ class PeerGradingModule(XModule):
randomization, and 5/7 on another randomization, and 5/7 on another
''' '''
max_grade = None 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 max_grade = self.max_grade
return max_grade return max_grade
...@@ -454,11 +454,13 @@ class PeerGradingModule(XModule): ...@@ -454,11 +454,13 @@ class PeerGradingModule(XModule):
except GradingServiceError: except GradingServiceError:
#This is a student_facing_error #This is a student_facing_error
error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR
log.error(error_text)
success = False success = False
# catch error if if the json loads fails # catch error if if the json loads fails
except ValueError: except ValueError:
#This is a student_facing_error #This is a student_facing_error
error_text = "Could not get list of problems to peer grade. Please notify course staff." error_text = "Could not get list of problems to peer grade. Please notify course staff."
log.error(error_text)
success = False success = False
except: except:
log.exception("Could not contact peer grading service.") log.exception("Could not contact peer grading service.")
...@@ -481,8 +483,8 @@ class PeerGradingModule(XModule): ...@@ -481,8 +483,8 @@ class PeerGradingModule(XModule):
problem_location = problem['location'] problem_location = problem['location']
descriptor = _find_corresponding_module_for_location(problem_location) descriptor = _find_corresponding_module_for_location(problem_location)
if descriptor: if descriptor:
problem['due'] = descriptor.metadata.get('peer_grading_due', None) problem['due'] = descriptor._model_data.get('peer_grading_due', None)
grace_period_string = descriptor.metadata.get('graceperiod', None) grace_period_string = descriptor._model_data.get('graceperiod', None)
try: try:
problem_timeinfo = TimeInfo(problem['due'], grace_period_string) problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
except: except:
...@@ -517,7 +519,7 @@ class PeerGradingModule(XModule): ...@@ -517,7 +519,7 @@ class PeerGradingModule(XModule):
Show individual problem interface Show individual problem interface
''' '''
if get is None or get.get('location') is None: 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 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 #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.") 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): ...@@ -565,40 +567,3 @@ class PeerGradingDescriptor(RawDescriptor):
stores_state = True stores_state = True
has_score = True has_score = True
template_dir_name = "peer_grading" 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): ...@@ -127,7 +127,7 @@ class OpenEndedChildTest(unittest.TestCase):
def test_reset(self): def test_reset(self):
self.openendedchild.reset(test_system) self.openendedchild.reset(test_system)
state = json.loads(self.openendedchild.get_instance_state()) 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): def test_is_last_response_correct(self):
...@@ -211,7 +211,7 @@ class OpenEndedModuleTest(unittest.TestCase): ...@@ -211,7 +211,7 @@ class OpenEndedModuleTest(unittest.TestCase):
self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY) self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY)
state = json.loads(self.openendedmodule.get_instance_state()) 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): def test_send_to_grader(self):
submission = "This is a student submission" submission = "This is a student submission"
...@@ -336,7 +336,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ...@@ -336,7 +336,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
descriptor = Mock() descriptor = Mock()
def setUp(self): 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): def test_get_tag_name(self):
name = self.combinedoe.get_tag_name("<t>Tag</t>") name = self.combinedoe.get_tag_name("<t>Tag</t>")
......
...@@ -52,6 +52,7 @@ class SelfAssessmentTest(unittest.TestCase): ...@@ -52,6 +52,7 @@ class SelfAssessmentTest(unittest.TestCase):
} }
self.module = SelfAssessmentModule(test_system, self.location, self.module = SelfAssessmentModule(test_system, self.location,
self.definition,
self.descriptor, self.descriptor,
static_data) static_data)
...@@ -80,18 +81,18 @@ class SelfAssessmentTest(unittest.TestCase): ...@@ -80,18 +81,18 @@ class SelfAssessmentTest(unittest.TestCase):
self.assertEqual(self.module.get_score()['score'], 0) self.assertEqual(self.module.get_score()['score'], 0)
self.module.save_answer({'student_answer': "I am an answer"}, test_system) 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.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({}) d = self.module.reset({})
self.assertTrue(d['success']) 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 # if we now assess as right, skip the REQUEST_HINT state
self.module.save_answer({'student_answer': 'answer 4'}, test_system) self.module.save_answer({'student_answer': 'answer 4'}, test_system)
responses['assessment'] = '1' responses['assessment'] = '1'
self.module.save_assessment(mock_query_dict, test_system) 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)
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