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):
......
...@@ -81,7 +81,7 @@ class CombinedOpenEndedV1Module(): ...@@ -81,7 +81,7 @@ class CombinedOpenEndedV1Module():
TEMPLATE_DIR = "combinedopenended" TEMPLATE_DIR = "combinedopenended"
def __init__(self, system, location, definition, descriptor, 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: Definition file should have one or many task blocks, a rubric block, and a prompt block:
...@@ -118,6 +118,7 @@ class CombinedOpenEndedV1Module(): ...@@ -118,6 +118,7 @@ class CombinedOpenEndedV1Module():
""" """
self._model_data = model_data
self.instance_state = instance_state self.instance_state = instance_state
self.display_name = instance_state.get('display_name', "Open Ended") self.display_name = instance_state.get('display_name', "Open Ended")
self.rewrite_content_links = static_data.get('rewrite_content_links',"") self.rewrite_content_links = static_data.get('rewrite_content_links',"")
...@@ -133,14 +134,14 @@ class CombinedOpenEndedV1Module(): ...@@ -133,14 +134,14 @@ class CombinedOpenEndedV1Module():
#Overall state of the combined open ended module #Overall state of the combined open ended module
self.state = instance_state.get('state', self.INITIAL) 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 #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.ready_to_reset = instance_state.get('ready_to_reset', False)
self.max_attempts = int(self.instance_state.get('attempts', MAX_ATTEMPTS)) self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS)
self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT 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.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) display_due_date_string = self.instance_state.get('due', None)
...@@ -154,7 +155,7 @@ class CombinedOpenEndedV1Module(): ...@@ -154,7 +155,7 @@ class CombinedOpenEndedV1Module():
# 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 = 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) self.rubric_renderer = CombinedOpenEndedRubric(system, True)
rubric_string = stringify_children(definition['rubric']) rubric_string = stringify_children(definition['rubric'])
...@@ -163,7 +164,7 @@ class CombinedOpenEndedV1Module(): ...@@ -163,7 +164,7 @@ class CombinedOpenEndedV1Module():
#Static data is passed to the child modules to render #Static data is passed to the child modules to render
self.static_data = { self.static_data = {
'max_score': self._max_score, 'max_score': self._max_score,
'max_attempts': self.max_attempts, 'max_attempts': self.attempts,
'prompt': definition['prompt'], 'prompt': definition['prompt'],
'rubric': definition['rubric'], 'rubric': definition['rubric'],
'display_name': self.display_name, 'display_name': self.display_name,
...@@ -197,10 +198,10 @@ class CombinedOpenEndedV1Module(): ...@@ -197,10 +198,10 @@ class CombinedOpenEndedV1Module():
last_response = last_response_data['response'] last_response = last_response_data['response']
loaded_task_state = json.loads(current_task_state) loaded_task_state = json.loads(current_task_state)
if loaded_task_state['state'] == self.INITIAL: if loaded_task_state['child_state'] == self.INITIAL:
loaded_task_state['state'] = self.ASSESSING loaded_task_state['child_state'] = self.ASSESSING
loaded_task_state['created'] = True loaded_task_state['child_created'] = True
loaded_task_state['history'].append({'answer': last_response}) loaded_task_state['child_history'].append({'answer': last_response})
current_task_state = json.dumps(loaded_task_state) current_task_state = json.dumps(loaded_task_state)
return current_task_state return current_task_state
...@@ -239,8 +240,8 @@ class CombinedOpenEndedV1Module(): ...@@ -239,8 +240,8 @@ class CombinedOpenEndedV1Module():
self.current_task_xml = self.task_xml[self.current_task_number] self.current_task_xml = self.task_xml[self.current_task_number]
if self.current_task_number > 0: if self.current_task_number > 0:
self.allow_reset = self.check_allow_reset() self.ready_to_reset = self.check_allow_reset()
if self.allow_reset: if self.ready_to_reset:
self.current_task_number = self.current_task_number - 1 self.current_task_number = self.current_task_number - 1
current_task_type = self.get_tag_name(self.current_task_xml) current_task_type = self.get_tag_name(self.current_task_xml)
...@@ -265,12 +266,12 @@ class CombinedOpenEndedV1Module(): ...@@ -265,12 +266,12 @@ class CombinedOpenEndedV1Module():
last_response_data = self.get_last_response(self.current_task_number - 1) last_response_data = self.get_last_response(self.current_task_number - 1)
last_response = last_response_data['response'] last_response = last_response_data['response']
current_task_state = json.dumps({ current_task_state = json.dumps({
'state': self.ASSESSING, 'child_state': self.ASSESSING,
'version': self.STATE_VERSION, 'version': self.STATE_VERSION,
'max_score': self._max_score, 'max_score': self._max_score,
'attempts': 0, 'child_attempts': 0,
'created': True, 'child_created': True,
'history': [{'answer': last_response}], 'child_history': [{'answer': last_response}],
}) })
self.current_task = child_task_module(self.system, self.location, self.current_task = child_task_module(self.system, self.location,
self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data,
...@@ -293,7 +294,7 @@ class CombinedOpenEndedV1Module(): ...@@ -293,7 +294,7 @@ class CombinedOpenEndedV1Module():
Input: None Input: None
Output: the allow_reset attribute of the current module. 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: if self.current_task_number > 0:
last_response_data = self.get_last_response(self.current_task_number - 1) last_response_data = self.get_last_response(self.current_task_number - 1)
current_response_data = self.get_current_attributes(self.current_task_number) current_response_data = self.get_current_attributes(self.current_task_number)
...@@ -301,9 +302,9 @@ class CombinedOpenEndedV1Module(): ...@@ -301,9 +302,9 @@ class CombinedOpenEndedV1Module():
if(current_response_data['min_score_to_attempt'] > last_response_data['score'] if(current_response_data['min_score_to_attempt'] > last_response_data['score']
or current_response_data['max_score_to_attempt'] < last_response_data['score']): or current_response_data['max_score_to_attempt'] < last_response_data['score']):
self.state = self.DONE 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): def get_context(self):
""" """
...@@ -317,7 +318,7 @@ class CombinedOpenEndedV1Module(): ...@@ -317,7 +318,7 @@ class CombinedOpenEndedV1Module():
context = { context = {
'items': [{'content': task_html}], 'items': [{'content': task_html}],
'ajax_url': self.system.ajax_url, 'ajax_url': self.system.ajax_url,
'allow_reset': self.allow_reset, 'allow_reset': self.ready_to_reset,
'state': self.state, 'state': self.state,
'task_count': len(self.task_xml), 'task_count': len(self.task_xml),
'task_number': self.current_task_number + 1, 'task_number': self.current_task_number + 1,
...@@ -395,7 +396,7 @@ class CombinedOpenEndedV1Module(): ...@@ -395,7 +396,7 @@ class CombinedOpenEndedV1Module():
task_parsed_xml = task_descriptor.definition_from_xml(etree_xml, self.system) 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, 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_response = task.latest_answer()
last_score = task.latest_score() last_score = task.latest_score()
last_post_assessment = task.latest_post_assessment(self.system) last_post_assessment = task.latest_post_assessment(self.system)
...@@ -413,7 +414,7 @@ class CombinedOpenEndedV1Module(): ...@@ -413,7 +414,7 @@ class CombinedOpenEndedV1Module():
else: else:
last_post_evaluation = task.format_feedback_with_evaluation(self.system, last_post_assessment) last_post_evaluation = task.format_feedback_with_evaluation(self.system, last_post_assessment)
last_post_assessment = last_post_evaluation 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'] rubric_scores = rubric_data['rubric_scores']
grader_types = rubric_data['grader_types'] grader_types = rubric_data['grader_types']
feedback_items = rubric_data['feedback_items'] feedback_items = rubric_data['feedback_items']
...@@ -427,7 +428,7 @@ class CombinedOpenEndedV1Module(): ...@@ -427,7 +428,7 @@ class CombinedOpenEndedV1Module():
last_post_assessment = "" last_post_assessment = ""
last_correctness = task.is_last_response_correct() last_correctness = task.is_last_response_correct()
max_score = task.max_score() max_score = task.max_score()
state = task.state state = task.child_state
if task_type in HUMAN_TASK_TYPE: if task_type in HUMAN_TASK_TYPE:
human_task_name = HUMAN_TASK_TYPE[task_type] human_task_name = HUMAN_TASK_TYPE[task_type]
else: else:
...@@ -477,10 +478,10 @@ class CombinedOpenEndedV1Module(): ...@@ -477,10 +478,10 @@ class CombinedOpenEndedV1Module():
Output: boolean indicating whether or not the task state changed. Output: boolean indicating whether or not the task state changed.
""" """
changed = False 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() 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]) 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 self.current_task_number += 1
if self.current_task_number >= (len(self.task_xml)): if self.current_task_number >= (len(self.task_xml)):
self.state = self.DONE self.state = self.DONE
...@@ -626,7 +627,7 @@ class CombinedOpenEndedV1Module(): ...@@ -626,7 +627,7 @@ class CombinedOpenEndedV1Module():
Output: Dictionary to be rendered Output: Dictionary to be rendered
""" """
self.update_task_states() 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): def reset(self, get):
""" """
...@@ -635,26 +636,26 @@ class CombinedOpenEndedV1Module(): ...@@ -635,26 +636,26 @@ class CombinedOpenEndedV1Module():
Output: AJAX dictionary to tbe rendered Output: AJAX dictionary to tbe rendered
""" """
if self.state != self.DONE: if self.state != self.DONE:
if not self.allow_reset: if not self.ready_to_reset:
return self.out_of_sync_error(get) return self.out_of_sync_error(get)
if self.attempts > self.max_attempts: if self.student_attempts > self.attempts:
return { return {
'success': False, 'success': False,
#This is a student_facing_error #This is a student_facing_error
'error': ('You have attempted this question {0} times. ' 'error': ('You have attempted this question {0} times. '
'You are only allowed to attempt it {1} times.').format( '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.state = self.INITIAL
self.allow_reset = False self.ready_to_reset = False
for i in xrange(0, len(self.task_xml)): for i in xrange(0, len(self.task_xml)):
self.current_task_number = i self.current_task_number = i
self.setup_next_task(reset=True) self.setup_next_task(reset=True)
self.current_task.reset(self.system) self.current_task.reset(self.system)
self.task_states[self.current_task_number] = self.current_task.get_instance_state() self.task_states[self.current_task_number] = self.current_task.get_instance_state()
self.current_task_number = 0 self.current_task_number = 0
self.allow_reset = False self.ready_to_reset = False
self.setup_next_task() self.setup_next_task()
return {'success': True, 'html': self.get_html_nonsystem()} return {'success': True, 'html': self.get_html_nonsystem()}
...@@ -670,8 +671,8 @@ class CombinedOpenEndedV1Module(): ...@@ -670,8 +671,8 @@ class CombinedOpenEndedV1Module():
'current_task_number': self.current_task_number, 'current_task_number': self.current_task_number,
'state': self.state, 'state': self.state,
'task_states': self.task_states, 'task_states': self.task_states,
'attempts': self.attempts, 'student_attempts': self.student_attempts,
'ready_to_reset': self.allow_reset, 'ready_to_reset': self.ready_to_reset,
} }
return json.dumps(state) return json.dumps(state)
...@@ -705,7 +706,7 @@ class CombinedOpenEndedV1Module(): ...@@ -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. 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: 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): def get_score(self):
""" """
......
...@@ -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