From dc7c3914542fde4ce2fd10c469548127293b39f2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 4 Mar 2013 19:14:38 -0500 Subject: [PATCH 01/12] Converting combined open ended module --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 11 ++++------- .../combined_open_ended_modulev1.py | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index b04d2d8aca..dc5ff36c7a 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -14,7 +14,7 @@ log = logging.getLogger("mitx.courseware") V1_ATTRIBUTES = ["display_name", "current_task_number", "task_states", "state", "attempts", "ready_to_reset", "max_attempts", "is_graded", "accept_file_upload", - "skip_spelling_checks", "due", "graceperiod", "max_score", "data"] + "skip_spelling_checks", "due", "graceperiod", "max_score"] VERSION_TUPLES = ( ('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_ATTRIBUTES), @@ -137,8 +137,9 @@ class CombinedOpenEndedModule(XModule): static_data = { 'rewrite_content_links' : self.rewrite_content_links, } - - instance_state = { k: self.__dict__[k] for k in self.__dict__ if k in attributes[version_index]} + instance_state = { k: getattr(self,k) for k in attributes[version_index]} + log.debug(instance_state) + instance_state.update({'data' : self.data}) self.child_descriptor = descriptors[version_index](self.system) self.child_definition = descriptors[version_index].definition_from_xml(etree.fromstring(self.data), self.system) self.child_module = modules[version_index](self.system, location, self.child_definition, self.child_descriptor, @@ -166,10 +167,6 @@ class CombinedOpenEndedModule(XModule): def due_date(self): return self.child_module.due_date - @property - def display_name(self): - return self.child_module.display_name - class CombinedOpenEndedDescriptor(RawDescriptor): """ diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 526c5ea4b3..4851582f98 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -116,6 +116,7 @@ class CombinedOpenEndedV1Module(): """ self.instance_state = instance_state + log.debug(instance_state) self.display_name = instance_state.get('display_name', "Open Ended") self.rewrite_content_links = static_data.get('rewrite_content_links',"") From 2924bd2f53e4d0dc914ef4bc3813248c7a5b1b7a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 5 Mar 2013 10:20:26 -0500 Subject: [PATCH 02/12] wip --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 1 - .../combined_open_ended_modulev1.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index dc5ff36c7a..3ea5dc3339 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -138,7 +138,6 @@ class CombinedOpenEndedModule(XModule): 'rewrite_content_links' : self.rewrite_content_links, } instance_state = { k: getattr(self,k) for k in attributes[version_index]} - log.debug(instance_state) instance_state.update({'data' : self.data}) self.child_descriptor = descriptors[version_index](self.system) self.child_definition = descriptors[version_index].definition_from_xml(etree.fromstring(self.data), self.system) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 4851582f98..55d8a2a009 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -135,7 +135,7 @@ class CombinedOpenEndedV1Module(): #Allow reset is true if student has failed the criteria to move to the next child task self.allow_reset = instance_state.get('ready_to_reset', False) - self.max_attempts = int(self.instance_state.get('attempts', MAX_ATTEMPTS)) + self.max_attempts = self.instance_state.get('attempts', MAX_ATTEMPTS) self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) @@ -152,7 +152,7 @@ class CombinedOpenEndedV1Module(): # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = int(self.instance_state.get('max_score', MAX_SCORE)) + self._max_score = self.instance_state.get('max_score', MAX_SCORE) self.rubric_renderer = CombinedOpenEndedRubric(system, True) rubric_string = stringify_children(definition['rubric']) From 697e426ce65af5cdb25dec0ef1498657ada94063 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 5 Mar 2013 16:58:59 -0500 Subject: [PATCH 03/12] Switch to passing through entire task state --- .../xmodule/combined_open_ended_module.py | 6 +++--- .../combined_open_ended_modulev1.py | 12 +++++++----- .../combined_open_ended_rubric.py | 2 +- .../open_ended_grading_classes/openendedchild.py | 16 +++++++++++----- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 3ea5dc3339..32fcc267f0 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -142,7 +142,7 @@ class CombinedOpenEndedModule(XModule): self.child_descriptor = descriptors[version_index](self.system) self.child_definition = descriptors[version_index].definition_from_xml(etree.fromstring(self.data), self.system) self.child_module = modules[version_index](self.system, location, self.child_definition, self.child_descriptor, - instance_state = instance_state, static_data= static_data) + instance_state = instance_state, static_data= static_data, model_data=model_data) def get_html(self): return self.child_module.get_html() @@ -156,8 +156,8 @@ class CombinedOpenEndedModule(XModule): def get_score(self): return self.child_module.get_score() - def max_score(self): - return self.child_module.max_score() + #def max_score(self): + # return self.child_module.max_score() def get_progress(self): return self.child_module.get_progress() diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 55d8a2a009..d51129152a 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -78,7 +78,7 @@ class CombinedOpenEndedV1Module(): DONE = 'done' 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: @@ -115,8 +115,8 @@ class CombinedOpenEndedV1Module(): """ + self._model_data = model_data self.instance_state = instance_state - log.debug(instance_state) self.display_name = instance_state.get('display_name', "Open Ended") self.rewrite_content_links = static_data.get('rewrite_content_links',"") @@ -233,7 +233,9 @@ class CombinedOpenEndedV1Module(): current_task_state = None if len(self.task_states) > self.current_task_number: current_task_state = self.task_states[self.current_task_number] + model_data = self._model_data['task_states'][self.current_task_number] + log.debug(model_data) self.current_task_xml = self.task_xml[self.current_task_number] if self.current_task_number > 0: @@ -272,7 +274,7 @@ class CombinedOpenEndedV1Module(): }) self.current_task = child_task_module(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, - instance_state=current_task_state) + instance_state=self.instance_state, model_data = self._model_data, task_number = self.current_task_number) self.task_states.append(self.current_task.get_instance_state()) self.state = self.ASSESSING else: @@ -280,7 +282,7 @@ class CombinedOpenEndedV1Module(): current_task_state = self.overwrite_state(current_task_state) self.current_task = child_task_module(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, - instance_state=current_task_state) + instance_state=self.instance_state, model_data = self._model_data, task_number = self.current_task_number) return True @@ -393,7 +395,7 @@ class CombinedOpenEndedV1Module(): task_parsed_xml = task_descriptor.definition_from_xml(etree_xml, self.system) task = children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, - self.static_data, instance_state=task_state) + self.static_data, instance_state=task_state, model_data = self._model_data) last_response = task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment(self.system) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py index f756b2b853..e2bafa7a10 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_rubric.py @@ -96,7 +96,7 @@ class CombinedOpenEndedRubric(object): log.error(error_message) raise RubricParsingError(error_message) - if total != max_score: + if int(total) != int(max_score): #This is a staff_facing_error error_msg = "The max score {0} for problem {1} does not match the total number of points in the rubric {2}. Contact the learning sciences group for assistance.".format( max_score, location, total) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 2edc81e722..7134269774 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -65,7 +65,7 @@ class OpenEndedChild(object): } def __init__(self, system, location, definition, descriptor, static_data, - instance_state=None, shared_state=None, **kwargs): + instance_state=None, shared_state=None, model_data=None, task_number = None, **kwargs): # Load instance state if instance_state is not None: instance_state = json.loads(instance_state) @@ -76,13 +76,19 @@ class OpenEndedChild(object): # None for any element, and score and hint can be None for the last (current) # element. # Scores are on scale from 0 to max_score - self.history = instance_state.get('history', []) + self._model_data = model_data + task_state = {} + if task_number is not None: + self.task_number = task_number + if instance_state is not None: + task_state = - self.state = instance_state.get('state', self.INITIAL) + instance_state['task_states'][task_number]['history'] + instance_state['task_states'][task_number]['state']', self.INITIAL) - self.created = instance_state.get('created', False) + self.created = task_state.get('created', False) - self.attempts = instance_state.get('attempts', 0) + self.attempts = task_state.get('attempts', 0) self.max_attempts = static_data['max_attempts'] self.prompt = static_data['prompt'] From 904ed6c80ff72e9c8b9b0a3acbad15da9856e455 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 5 Mar 2013 17:48:39 -0500 Subject: [PATCH 04/12] Combined open ended renders correctly, but internal state tracking broken --- .../xmodule/combined_open_ended_module.py | 2 +- .../combined_open_ended_modulev1.py | 23 +++--- .../open_ended_module.py | 44 +++++------ .../openendedchild.py | 79 +++++++++++-------- .../self_assessment_module.py | 54 +++++-------- 5 files changed, 97 insertions(+), 105 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 32fcc267f0..e30cb51dca 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -58,7 +58,7 @@ class CombinedOpenEndedModule(XModule): display_name = String(help="Display name for this module", scope=Scope.settings) current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.student_state) - task_states = String(help="State dictionaries of each task within this module.", default=json.dumps("[]"), scope=Scope.student_state) + task_states = Object(help="State dictionaries of each task within this module.", default=[], scope=Scope.student_state) state = String(help="Which step within the current task that the student is on.", default="initial", scope=Scope.student_state) attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) ready_to_reset = Boolean(help="If the problem is ready to be reset or not.", default=False, scope=Scope.student_state) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index d51129152a..7ca6d2029e 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -195,10 +195,10 @@ class CombinedOpenEndedV1Module(): last_response = last_response_data['response'] loaded_task_state = json.loads(current_task_state) - if loaded_task_state['state'] == self.INITIAL: - loaded_task_state['state'] = self.ASSESSING - loaded_task_state['created'] = True - loaded_task_state['history'].append({'answer': last_response}) + if loaded_task_state['child_state'] == self.INITIAL: + loaded_task_state['child_state'] = self.ASSESSING + loaded_task_state['child_created'] = True + loaded_task_state['child_history'].append({'answer': last_response}) current_task_state = json.dumps(loaded_task_state) return current_task_state @@ -233,9 +233,7 @@ class CombinedOpenEndedV1Module(): current_task_state = None if len(self.task_states) > self.current_task_number: current_task_state = self.task_states[self.current_task_number] - model_data = self._model_data['task_states'][self.current_task_number] - log.debug(model_data) self.current_task_xml = self.task_xml[self.current_task_number] if self.current_task_number > 0: @@ -255,6 +253,7 @@ class CombinedOpenEndedV1Module(): #This sends the etree_xml object through the descriptor module of the current task, and #returns the xml parsed by the descriptor + log.debug(current_task_state) self.current_task_parsed_xml = self.current_task_descriptor.definition_from_xml(etree_xml, self.system) if current_task_state is None and self.current_task_number == 0: self.current_task = child_task_module(self.system, self.location, @@ -268,9 +267,9 @@ class CombinedOpenEndedV1Module(): 'state': self.ASSESSING, 'version': self.STATE_VERSION, 'max_score': self._max_score, - 'attempts': 0, - 'created': True, - 'history': [{'answer': last_response}], + 'child_attempts': 0, + 'child_created': True, + 'child_history': [{'answer': last_response}], }) self.current_task = child_task_module(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, @@ -395,7 +394,7 @@ class CombinedOpenEndedV1Module(): task_parsed_xml = task_descriptor.definition_from_xml(etree_xml, self.system) task = children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, - self.static_data, instance_state=task_state, model_data = self._model_data) + self.static_data, instance_state=self.instance_state, model_data = self._model_data) last_response = task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment(self.system) @@ -413,7 +412,7 @@ class CombinedOpenEndedV1Module(): else: last_post_evaluation = task.format_feedback_with_evaluation(self.system, last_post_assessment) last_post_assessment = last_post_evaluation - rubric_data = task._parse_score_msg(task.history[-1].get('post_assessment', ""), self.system) + rubric_data = task._parse_score_msg(task.child_history[-1].get('post_assessment', ""), self.system) rubric_scores = rubric_data['rubric_scores'] grader_types = rubric_data['grader_types'] feedback_items = rubric_data['feedback_items'] @@ -427,7 +426,7 @@ class CombinedOpenEndedV1Module(): last_post_assessment = "" last_correctness = task.is_last_response_correct() max_score = task.max_score() - state = task.state + state = task.child_state if task_type in HUMAN_TASK_TYPE: human_task_name = HUMAN_TASK_TYPE[task_type] else: diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py index 7395a93a0b..022509b659 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py @@ -63,17 +63,17 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if oeparam is None: #This is a staff_facing_error raise ValueError(error_message.format('oeparam')) - if self.prompt is None: + if self.child_prompt is None: raise ValueError(error_message.format('prompt')) - if self.rubric is None: + if self.child_rubric is None: raise ValueError(error_message.format('rubric')) - self._parse(oeparam, self.prompt, self.rubric, system) + self._parse(oeparam, self.child_prompt, self.child_rubric, system) - if self.created == True and self.state == self.ASSESSING: - self.created = False + if self.child_created == True and self.child_state == self.ASSESSING: + self.child_created = False self.send_to_grader(self.latest_answer(), system) - self.created = False + self.child_created = False def _parse(self, oeparam, prompt, rubric, system): @@ -88,8 +88,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload prompt_string = stringify_children(prompt) rubric_string = stringify_children(rubric) - self.prompt = prompt_string - self.rubric = rubric_string + self.child_prompt = prompt_string + self.child_rubric = rubric_string grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -128,7 +128,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): @param system: ModuleSystem @return: Success indicator """ - self.state = self.DONE + self.child_state = self.DONE return {'success': True} def message_post(self, get, system): @@ -166,7 +166,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): anonymous_student_id = system.anonymous_student_id queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + - str(len(self.history))) + str(len(self.child_history))) xheader = xqueue_interface.make_xheader( lms_callback_url=system.xqueue['callback_url'], @@ -193,7 +193,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if error: success = False - self.state = self.DONE + self.child_state = self.DONE #This is a student_facing_message return {'success': success, 'msg': "Successfully submitted your feedback."} @@ -217,7 +217,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # Generate header queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + - str(len(self.history))) + str(len(self.child_history))) xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'], lms_key=queuekey, @@ -260,7 +260,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.record_latest_score(new_score_msg['score']) self.record_latest_post_assessment(score_msg) - self.state = self.POST_ASSESSMENT + self.child_state = self.POST_ASSESSMENT return True @@ -539,16 +539,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild): @param short_feedback: If the long feedback is wanted or not @return: Returns formatted feedback """ - if not self.history: + if not self.child_history: return "" - feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', ""), system, + feedback_dict = self._parse_score_msg(self.child_history[-1].get('post_assessment', ""), system, join_feedback=join_feedback) if not short_feedback: return feedback_dict['feedback'] if feedback_dict['valid'] else '' if feedback_dict['valid']: short_feedback = self._convert_longform_feedback_to_html( - json.loads(self.history[-1].get('post_assessment', ""))) + json.loads(self.child_history[-1].get('post_assessment', ""))) return short_feedback if feedback_dict['valid'] else '' def format_feedback_with_evaluation(self, system, feedback): @@ -601,7 +601,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): @param system: Modulesystem (needed to align with other ajax functions) @return: Returns the current state """ - state = self.state + state = self.child_state return {'state': state} def save_answer(self, get, system): @@ -617,7 +617,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if closed: return msg - if self.state != self.INITIAL: + if self.child_state != self.INITIAL: return self.out_of_sync_error(get) # add new history element with answer and empty score and hint. @@ -664,13 +664,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): """ #set context variables and render template eta_string = None - if self.state != self.INITIAL: + if self.child_state != self.INITIAL: latest = self.latest_answer() previous_answer = latest if latest is not None else self.initial_display post_assessment = self.latest_post_assessment(system) score = self.latest_score() correct = 'correct' if self.is_submission_correct(score) else 'incorrect' - if self.state == self.ASSESSING: + if self.child_state == self.ASSESSING: eta_string = self.get_eta() else: post_assessment = "" @@ -679,9 +679,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): context = { - 'prompt': self.prompt, + 'prompt': self.child_prompt, 'previous_answer': previous_answer, - 'state': self.state, + 'state': self.child_state, 'allow_reset': self._allow_reset(), 'rows': 30, 'cols': 80, diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 7134269774..844d0279c8 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -67,8 +67,12 @@ class OpenEndedChild(object): def __init__(self, system, location, definition, descriptor, static_data, instance_state=None, shared_state=None, model_data=None, task_number = None, **kwargs): # Load instance state + if instance_state is not None: - instance_state = json.loads(instance_state) + try: + instance_state = json.loads(instance_state) + except: + pass else: instance_state = {} @@ -78,30 +82,37 @@ class OpenEndedChild(object): # Scores are on scale from 0 to max_score self._model_data = model_data task_state = {} - if task_number is not None: - self.task_number = task_number - if instance_state is not None: - task_state = - instance_state['task_states'][task_number]['history'] - instance_state['task_states'][task_number]['state']', self.INITIAL) + try: + self.child_history=instance_state['task_states'][task_number]['history'] + except: + self.child_history = [] + try: + self.child_state=instance_state['task_states'][task_number]['state'] + except: + self.child_state = self.INITIAL - self.created = task_state.get('created', False) + try: + self.child_created = instance_state['task_states'][task_number]['created'] + except: + self.child_created = False - self.attempts = task_state.get('attempts', 0) - self.max_attempts = static_data['max_attempts'] + try: + self.child_attempts = instance_state['task_states'][task_number]['attempts'] + except: + self.child_attempts = 0 - self.prompt = static_data['prompt'] - self.rubric = static_data['rubric'] + self.child_prompt = static_data['prompt'] + self.child_rubric = static_data['rubric'] self.display_name = static_data['display_name'] self.accept_file_upload = static_data['accept_file_upload'] self.close_date = static_data['close_date'] self.s3_interface = static_data['s3_interface'] self.skip_basic_checks = static_data['skip_basic_checks'] + self._max_score = static_data['max_score'] # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = static_data['max_score'] if system.open_ended_grading_interface: self.peer_gs = PeerGradingService(system.open_ended_grading_interface, system) self.controller_qs = controller_query_service.ControllerQueryService(system.open_ended_grading_interface,system) @@ -109,8 +120,6 @@ class OpenEndedChild(object): self.peer_gs = MockPeerGradingService() self.controller_qs = None - - self.system = system self.location_string = location @@ -144,32 +153,32 @@ class OpenEndedChild(object): #This is a student_facing_error 'error': 'The problem close date has passed, and this problem is now closed.' } - elif self.attempts > self.max_attempts: + elif self.child_attempts > self.max_attempts: return True, { 'success': False, #This is a student_facing_error - 'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.attempts, self.max_attempts) + 'error': 'You have attempted this problem {0} times. You are allowed {1} attempts.'.format(self.child_attempts, self.max_attempts) } else: return False, {} def latest_answer(self): """Empty string if not available""" - if not self.history: + if not self.child_history: return "" - return self.history[-1].get('answer', "") + return self.child_history[-1].get('answer', "") def latest_score(self): """None if not available""" - if not self.history: + if not self.child_history: return None - return self.history[-1].get('score') + return self.child_history[-1].get('score') def latest_post_assessment(self, system): """Empty string if not available""" - if not self.history: + if not self.child_history: return "" - return self.history[-1].get('post_assessment', "") + return self.child_history[-1].get('post_assessment', "") @staticmethod def sanitize_html(answer): @@ -191,30 +200,30 @@ class OpenEndedChild(object): @return: None """ answer = OpenEndedChild.sanitize_html(answer) - self.history.append({'answer': answer}) + self.child_history.append({'answer': answer}) def record_latest_score(self, score): """Assumes that state is right, so we're adding a score to the latest history element""" - self.history[-1]['score'] = score + self.child_history[-1]['score'] = score def record_latest_post_assessment(self, post_assessment): """Assumes that state is right, so we're adding a score to the latest history element""" - self.history[-1]['post_assessment'] = post_assessment + self.child_history[-1]['post_assessment'] = post_assessment def change_state(self, new_state): """ A centralized place for state changes--allows for hooks. If the current state matches the old state, don't run any hooks. """ - if self.state == new_state: + if self.child_state == new_state: return - self.state = new_state + self.child_state = new_state - if self.state == self.DONE: - self.attempts += 1 + if self.child_state == self.DONE: + self.child_attempts += 1 def get_instance_state(self): """ @@ -223,17 +232,17 @@ class OpenEndedChild(object): state = { 'version': self.STATE_VERSION, - 'history': self.history, - 'state': self.state, + 'history': self.child_history, + 'state': self.child_state, 'max_score': self._max_score, - 'attempts': self.attempts, + 'attempts': self.child_attempts, 'created': False, } return json.dumps(state) def _allow_reset(self): """Can the module be reset?""" - return (self.state == self.DONE and self.attempts < self.max_attempts) + return (self.child_state == self.DONE and self.child_attempts < self.max_attempts) def max_score(self): """ @@ -278,7 +287,7 @@ class OpenEndedChild(object): """ #This is a dev_facing_error log.warning("Open ended child state out sync. state: %r, get: %r. %s", - self.state, get, msg) + self.child_state, get, msg) #This is a student_facing_error return {'success': False, 'error': 'The problem state got out-of-sync. Please try reloading the page.'} diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py index dac0747095..6ffe08fb02 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py @@ -39,22 +39,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): REQUEST_HINT = 'request_hint' DONE = 'done' - student_answers = List(scope=Scope.student_state, default=[]) - scores = List(scope=Scope.student_state, default=[]) - hints = List(scope=Scope.student_state, default=[]) - state = String(scope=Scope.student_state, default=INITIAL) - - # Used for progress / grading. Currently get credit just for - # completion (doesn't matter if you self-assessed correct/incorrect). - max_score = Integer(scope=Scope.settings, default=openendedchild.MAX_SCORE) - max_attempts = Integer(scope=Scope.settings, default=openendedchild.MAX_ATTEMPTS) - - attempts = Integer(scope=Scope.student_state, default=0) - rubric = String(scope=Scope.content) - prompt = String(scope=Scope.content) - submitmessage = String(scope=Scope.content) - hintprompt = String(scope=Scope.content) - def setup_response(self, system, location, definition, descriptor): """ Sets up the module @@ -64,8 +48,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): @param descriptor: SelfAssessmentDescriptor @return: None """ - self.prompt = stringify_children(self.prompt) - self.rubric = stringify_children(self.rubric) + self.child_prompt = stringify_children(self.child_prompt) + self.child_rubric = stringify_children(self.child_rubric) def get_html(self, system): """ @@ -74,18 +58,18 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): @return: Rendered HTML """ #set context variables and render template - if self.state != self.INITIAL: + if self.child_state != self.INITIAL: latest = self.latest_answer() previous_answer = latest if latest is not None else '' else: previous_answer = '' context = { - 'prompt': self.prompt, + 'prompt': self.child_prompt, 'previous_answer': previous_answer, 'ajax_url': system.ajax_url, 'initial_rubric': self.get_rubric_html(system), - 'state': self.state, + 'state': self.child_state, 'allow_reset': self._allow_reset(), 'child_type': 'selfassessment', 'accept_file_upload': self.accept_file_upload, @@ -131,11 +115,11 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): """ Return the appropriate version of the rubric, based on the state. """ - if self.state == self.INITIAL: + if self.child_state == self.INITIAL: return '' rubric_renderer = CombinedOpenEndedRubric(system, False) - rubric_dict = rubric_renderer.render_rubric(self.rubric) + rubric_dict = rubric_renderer.render_rubric(self.child_rubric) success = rubric_dict['success'] rubric_html = rubric_dict['html'] @@ -144,13 +128,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): 'max_score': self._max_score, } - if self.state == self.ASSESSING: + if self.child_state == self.ASSESSING: context['read_only'] = False - elif self.state in (self.POST_ASSESSMENT, self.DONE): + elif self.child_state in (self.POST_ASSESSMENT, self.DONE): context['read_only'] = True else: #This is a dev_facing_error - raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.state)) + raise ValueError("Self assessment module is in an illegal state '{0}'".format(self.child_state)) return system.render_template('self_assessment_rubric.html', context) @@ -158,10 +142,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): """ Return the appropriate version of the hint view, based on state. """ - if self.state in (self.INITIAL, self.ASSESSING): + if self.child_state in (self.INITIAL, self.ASSESSING): return '' - if self.state == self.DONE: + if self.child_state == self.DONE: # display the previous hint latest = self.latest_post_assessment(system) hint = latest if latest is not None else '' @@ -170,13 +154,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): context = {'hint': hint} - if self.state == self.POST_ASSESSMENT: + if self.child_state == self.POST_ASSESSMENT: context['read_only'] = False - elif self.state == self.DONE: + elif self.child_state == self.DONE: context['read_only'] = True else: #This is a dev_facing_error - raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.state)) + raise ValueError("Self Assessment module is in an illegal state '{0}'".format(self.child_state)) return system.render_template('self_assessment_hint.html', context) @@ -198,7 +182,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if closed: return msg - if self.state != self.INITIAL: + if self.child_state != self.INITIAL: return self.out_of_sync_error(get) error_message = "" @@ -239,7 +223,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): 'message_html' only if success is true """ - if self.state != self.ASSESSING: + if self.child_state != self.ASSESSING: return self.out_of_sync_error(get) try: @@ -262,7 +246,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): self.change_state(self.DONE) d['allow_reset'] = self._allow_reset() - d['state'] = self.state + d['state'] = self.child_state return d def save_hint(self, get, system): @@ -276,7 +260,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): with the error key only present if success is False and message_html only if True. ''' - if self.state != self.POST_ASSESSMENT: + if self.child_state != self.POST_ASSESSMENT: # Note: because we only ask for hints on wrong answers, may not have # the same number of hints and answers. return self.out_of_sync_error(get) From 0d777a7a0e770d1bda0238b5e20977bdd7f84f74 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 5 Mar 2013 17:53:06 -0500 Subject: [PATCH 05/12] Convert to passing in current task state again --- .../combined_open_ended_modulev1.py | 4 ++-- .../xmodule/open_ended_grading_classes/openendedchild.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 7ca6d2029e..2149729605 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -273,7 +273,7 @@ class CombinedOpenEndedV1Module(): }) self.current_task = child_task_module(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, - instance_state=self.instance_state, model_data = self._model_data, task_number = self.current_task_number) + instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state = self.ASSESSING else: @@ -281,7 +281,7 @@ class CombinedOpenEndedV1Module(): current_task_state = self.overwrite_state(current_task_state) self.current_task = child_task_module(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, - instance_state=self.instance_state, model_data = self._model_data, task_number = self.current_task_number) + instance_state=current_task_state) return True diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 844d0279c8..8cdfc8f322 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -102,6 +102,7 @@ class OpenEndedChild(object): except: self.child_attempts = 0 + self.max_attempts = static_data['max_attempts'] self.child_prompt = static_data['prompt'] self.child_rubric = static_data['rubric'] self.display_name = static_data['display_name'] @@ -277,7 +278,7 @@ class OpenEndedChild(object): return Progress(self.get_score()['score'], self._max_score) except Exception as err: #This is a dev_facing_error - log.exception("Got bad progress from open ended child module. Max Score: {1}".format(self._max_score)) + log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score)) return None return None From b7c6f7ca1a9575f39bdc465dc3178b5483429325 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 5 Mar 2013 19:16:02 -0500 Subject: [PATCH 06/12] Mostly working state --- .../combined_open_ended_modulev1.py | 9 ++--- .../openendedchild.py | 36 ++++++------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 2149729605..8ad53f3db7 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -195,6 +195,7 @@ class CombinedOpenEndedV1Module(): last_response = last_response_data['response'] loaded_task_state = json.loads(current_task_state) + log.debug(loaded_task_state) if loaded_task_state['child_state'] == self.INITIAL: loaded_task_state['child_state'] = self.ASSESSING loaded_task_state['child_created'] = True @@ -253,7 +254,6 @@ class CombinedOpenEndedV1Module(): #This sends the etree_xml object through the descriptor module of the current task, and #returns the xml parsed by the descriptor - log.debug(current_task_state) self.current_task_parsed_xml = self.current_task_descriptor.definition_from_xml(etree_xml, self.system) if current_task_state is None and self.current_task_number == 0: self.current_task = child_task_module(self.system, self.location, @@ -264,7 +264,7 @@ class CombinedOpenEndedV1Module(): last_response_data = self.get_last_response(self.current_task_number - 1) last_response = last_response_data['response'] current_task_state = json.dumps({ - 'state': self.ASSESSING, + 'child_state': self.ASSESSING, 'version': self.STATE_VERSION, 'max_score': self._max_score, 'child_attempts': 0, @@ -276,6 +276,7 @@ class CombinedOpenEndedV1Module(): instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state = self.ASSESSING + log.debug(self.task_states) else: if self.current_task_number > 0 and not reset: current_task_state = self.overwrite_state(current_task_state) @@ -394,7 +395,7 @@ class CombinedOpenEndedV1Module(): task_parsed_xml = task_descriptor.definition_from_xml(etree_xml, self.system) task = children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, - self.static_data, instance_state=self.instance_state, model_data = self._model_data) + self.static_data, instance_state=task_state, model_data = self._model_data) last_response = task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment(self.system) @@ -479,7 +480,7 @@ class CombinedOpenEndedV1Module(): if not self.allow_reset: self.task_states[self.current_task_number] = self.current_task.get_instance_state() current_task_state = json.loads(self.task_states[self.current_task_number]) - if current_task_state['state'] == self.DONE: + if current_task_state['child_state'] == self.DONE: self.current_task_number += 1 if self.current_task_number >= (len(self.task_xml)): self.state = self.DONE diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py index 8cdfc8f322..9a9446b6cf 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py @@ -65,7 +65,7 @@ class OpenEndedChild(object): } def __init__(self, system, location, definition, descriptor, static_data, - instance_state=None, shared_state=None, model_data=None, task_number = None, **kwargs): + instance_state=None, shared_state=None, **kwargs): # Load instance state if instance_state is not None: @@ -80,27 +80,11 @@ class OpenEndedChild(object): # None for any element, and score and hint can be None for the last (current) # element. # Scores are on scale from 0 to max_score - self._model_data = model_data - task_state = {} - try: - self.child_history=instance_state['task_states'][task_number]['history'] - except: - self.child_history = [] - try: - self.child_state=instance_state['task_states'][task_number]['state'] - except: - self.child_state = self.INITIAL - - try: - self.child_created = instance_state['task_states'][task_number]['created'] - except: - self.child_created = False - - try: - self.child_attempts = instance_state['task_states'][task_number]['attempts'] - except: - self.child_attempts = 0 + 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.max_attempts = static_data['max_attempts'] self.child_prompt = static_data['prompt'] @@ -233,11 +217,11 @@ class OpenEndedChild(object): state = { 'version': self.STATE_VERSION, - 'history': self.child_history, - 'state': self.child_state, + 'child_history': self.child_history, + 'child_state': self.child_state, 'max_score': self._max_score, - 'attempts': self.child_attempts, - 'created': False, + 'child_attempts': self.child_attempts, + 'child_created': False, } return json.dumps(state) @@ -275,7 +259,7 @@ class OpenEndedChild(object): ''' if self._max_score > 0: try: - return Progress(self.get_score()['score'], self._max_score) + return Progress(int(self.get_score()['score']), int(self._max_score)) except Exception as err: #This is a dev_facing_error log.exception("Got bad progress from open ended child module. Max Score: {0}".format(self._max_score)) From 7ea3d70c2c9a97e7e9fc4c2607cc132cbe3a5802 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 11:21:42 -0500 Subject: [PATCH 07/12] Caching attribute values from child module --- .../xmodule/combined_open_ended_module.py | 35 +++++++++++++------ .../combined_open_ended_modulev1.py | 14 ++++---- .../open_ended_module.py | 1 + .../xmodule/xmodule/peer_grading_module.py | 5 ++- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index e30cb51dca..2a3931dffb 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -12,12 +12,16 @@ from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import Comb log = logging.getLogger("mitx.courseware") -V1_ATTRIBUTES = ["display_name", "current_task_number", "task_states", "state", - "attempts", "ready_to_reset", "max_attempts", "is_graded", "accept_file_upload", +V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload", "skip_spelling_checks", "due", "graceperiod", "max_score"] +V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state", + "student_attempts", "ready_to_reset"] + +V1_ATTRIBUTES = V1_SETTINGS_ATTRIBUTES + V1_STUDENT_ATTRIBUTES + VERSION_TUPLES = ( - ('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_ATTRIBUTES), + ('1', CombinedOpenEndedV1Descriptor, CombinedOpenEndedV1Module, V1_SETTINGS_ATTRIBUTES, V1_STUDENT_ATTRIBUTES), ) DEFAULT_VERSION = 1 @@ -56,13 +60,13 @@ class CombinedOpenEndedModule(XModule): icon_class = 'problem' - display_name = String(help="Display name for this module", scope=Scope.settings) + display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings) current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.student_state) task_states = Object(help="State dictionaries of each task within this module.", default=[], scope=Scope.student_state) state = String(help="Which step within the current task that the student is on.", default="initial", scope=Scope.student_state) - attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) + student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) ready_to_reset = Boolean(help="If the problem is ready to be reset or not.", default=False, scope=Scope.student_state) - max_attempts = Integer(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings) + attempts = Integer(help="Maximum number of attempts that a student is allowed.", default=1, scope=Scope.settings) is_graded = Boolean(help="Whether or not the problem is graded.", default=False, scope=Scope.settings) accept_file_upload = Boolean(help="Whether or not the problem accepts file uploads.", default=False, scope=Scope.settings) skip_spelling_checks = Boolean(help="Whether or not to skip initial spelling checks.", default=True, scope=Scope.settings) @@ -124,7 +128,8 @@ class CombinedOpenEndedModule(XModule): versions = [i[0] for i in VERSION_TUPLES] descriptors = [i[1] for i in VERSION_TUPLES] modules = [i[2] for i in VERSION_TUPLES] - attributes = [i[3] for i in VERSION_TUPLES] + settings_attributes = [i[3] for i in VERSION_TUPLES] + student_attributes = [i[4] for i in VERSION_TUPLES] try: version_index = versions.index(self.version) @@ -134,20 +139,26 @@ class CombinedOpenEndedModule(XModule): self.version = DEFAULT_VERSION version_index = versions.index(self.version) + self.student_attributes = student_attributes[version_index] + self.settings_attributes = settings_attributes[version_index] + + attributes = self.student_attributes + self.settings_attributes + static_data = { 'rewrite_content_links' : self.rewrite_content_links, } - instance_state = { k: getattr(self,k) for k in attributes[version_index]} - instance_state.update({'data' : self.data}) + instance_state = { k: getattr(self,k) for k in attributes} self.child_descriptor = descriptors[version_index](self.system) self.child_definition = descriptors[version_index].definition_from_xml(etree.fromstring(self.data), self.system) self.child_module = modules[version_index](self.system, location, self.child_definition, self.child_descriptor, - instance_state = instance_state, static_data= static_data, model_data=model_data) + instance_state = instance_state, static_data= static_data, model_data=model_data, attributes=attributes) def get_html(self): + self.save_instance_data() return self.child_module.get_html() def handle_ajax(self, dispatch, get): + self.save_instance_data() return self.child_module.handle_ajax(dispatch, get) def get_instance_state(self): @@ -166,6 +177,10 @@ class CombinedOpenEndedModule(XModule): def due_date(self): return self.child_module.due_date + def save_instance_date(self): + for attribute in self.student_attributes: + setattr(self,k, getattr(self.child_module,k)) + class CombinedOpenEndedDescriptor(RawDescriptor): """ diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 8ad53f3db7..4f304b1a0e 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -131,11 +131,11 @@ class CombinedOpenEndedV1Module(): #Overall state of the combined open ended module self.state = instance_state.get('state', self.INITIAL) - self.attempts = instance_state.get('attempts', 0) + self.student_attempts = instance_state.get('student_attempts', 0) #Allow reset is true if student has failed the criteria to move to the next child task self.allow_reset = instance_state.get('ready_to_reset', False) - self.max_attempts = 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.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) @@ -161,7 +161,7 @@ class CombinedOpenEndedV1Module(): #Static data is passed to the child modules to render self.static_data = { 'max_score': self._max_score, - 'max_attempts': self.max_attempts, + 'max_attempts': self.attempts, 'prompt': definition['prompt'], 'rubric': definition['rubric'], 'display_name': self.display_name, @@ -195,7 +195,6 @@ class CombinedOpenEndedV1Module(): last_response = last_response_data['response'] loaded_task_state = json.loads(current_task_state) - log.debug(loaded_task_state) if loaded_task_state['child_state'] == self.INITIAL: loaded_task_state['child_state'] = self.ASSESSING loaded_task_state['child_created'] = True @@ -276,7 +275,6 @@ class CombinedOpenEndedV1Module(): instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state = self.ASSESSING - log.debug(self.task_states) else: if self.current_task_number > 0 and not reset: current_task_state = self.overwrite_state(current_task_state) @@ -638,13 +636,13 @@ class CombinedOpenEndedV1Module(): if not self.allow_reset: return self.out_of_sync_error(get) - if self.attempts > self.max_attempts: + if self.student_attempts > self.attempts: return { 'success': False, #This is a student_facing_error 'error': ('You have attempted this question {0} times. ' 'You are only allowed to attempt it {1} times.').format( - self.attempts, self.max_attempts) + self.student_attempts, self.attempts) } self.state = self.INITIAL self.allow_reset = False @@ -670,7 +668,7 @@ class CombinedOpenEndedV1Module(): 'current_task_number': self.current_task_number, 'state': self.state, 'task_states': self.task_states, - 'attempts': self.attempts, + 'student_attempts': self.student_attempts, 'ready_to_reset': self.allow_reset, } diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py index 022509b659..a7b3f8f9ed 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py @@ -579,6 +579,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'check_for_score': self.check_for_score, } + log.debug(dispatch) if dispatch not in handlers: #This is a dev_facing_error log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch)) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index b469fd9656..e6651ed85e 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -454,11 +454,13 @@ class PeerGradingModule(XModule): except GradingServiceError: #This is a student_facing_error error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR + log.error(error_text) success = False # catch error if if the json loads fails except ValueError: #This is a student_facing_error error_text = "Could not get list of problems to peer grade. Please notify course staff." + log.error(error_text) success = False @@ -553,7 +555,7 @@ class PeerGradingModule(XModule): class PeerGradingDescriptor(RawDescriptor): """ - Module for adding peer grading questions + Module for adding combined open ended questions """ mako_template = "widgets/raw-edit.html" module_class = PeerGradingModule @@ -562,3 +564,4 @@ class PeerGradingDescriptor(RawDescriptor): stores_state = True has_score = True template_dir_name = "peer_grading" + From 3329af077ae31e359fd3e09341f64e686606ffa9 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 11:29:25 -0500 Subject: [PATCH 08/12] Force instance state to save periodically --- .../xmodule/combined_open_ended_module.py | 11 +++++--- .../combined_open_ended_modulev1.py | 28 +++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 2a3931dffb..4f806d6b78 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -155,11 +155,14 @@ class CombinedOpenEndedModule(XModule): def get_html(self): self.save_instance_data() - return self.child_module.get_html() + return_value = self.child_module.get_html() + return return_value def handle_ajax(self, dispatch, get): self.save_instance_data() - return self.child_module.handle_ajax(dispatch, get) + return_value = self.child_module.handle_ajax(dispatch, get) + self.save_instance_data() + return return_value def get_instance_state(self): return self.child_module.get_instance_state() @@ -177,9 +180,9 @@ class CombinedOpenEndedModule(XModule): def due_date(self): return self.child_module.due_date - def save_instance_date(self): + def save_instance_data(self): for attribute in self.student_attributes: - setattr(self,k, getattr(self.child_module,k)) + setattr(self,attribute, getattr(self.child_module,attribute)) class CombinedOpenEndedDescriptor(RawDescriptor): diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 4f304b1a0e..7b768457ba 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -134,7 +134,7 @@ class CombinedOpenEndedV1Module(): self.student_attempts = instance_state.get('student_attempts', 0) #Allow reset is true if student has failed the criteria to move to the next child task - self.allow_reset = instance_state.get('ready_to_reset', False) + self.ready_to_reset = instance_state.get('ready_to_reset', False) self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS) self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT @@ -237,8 +237,8 @@ class CombinedOpenEndedV1Module(): self.current_task_xml = self.task_xml[self.current_task_number] if self.current_task_number > 0: - self.allow_reset = self.check_allow_reset() - if self.allow_reset: + self.ready_to_reset = self.check_allow_reset() + if self.ready_to_reset: self.current_task_number = self.current_task_number - 1 current_task_type = self.get_tag_name(self.current_task_xml) @@ -291,7 +291,7 @@ class CombinedOpenEndedV1Module(): Input: None Output: the allow_reset attribute of the current module. """ - if not self.allow_reset: + if not self.ready_to_reset: if self.current_task_number > 0: last_response_data = self.get_last_response(self.current_task_number - 1) current_response_data = self.get_current_attributes(self.current_task_number) @@ -299,9 +299,9 @@ class CombinedOpenEndedV1Module(): if(current_response_data['min_score_to_attempt'] > last_response_data['score'] or current_response_data['max_score_to_attempt'] < last_response_data['score']): self.state = self.DONE - self.allow_reset = True + self.ready_to_reset = True - return self.allow_reset + return self.ready_to_reset def get_context(self): """ @@ -315,7 +315,7 @@ class CombinedOpenEndedV1Module(): context = { 'items': [{'content': task_html}], 'ajax_url': self.system.ajax_url, - 'allow_reset': self.allow_reset, + 'allow_reset': self.ready_to_reset, 'state': self.state, 'task_count': len(self.task_xml), 'task_number': self.current_task_number + 1, @@ -475,7 +475,7 @@ class CombinedOpenEndedV1Module(): Output: boolean indicating whether or not the task state changed. """ changed = False - if not self.allow_reset: + if not self.ready_to_reset: self.task_states[self.current_task_number] = self.current_task.get_instance_state() current_task_state = json.loads(self.task_states[self.current_task_number]) if current_task_state['child_state'] == self.DONE: @@ -624,7 +624,7 @@ class CombinedOpenEndedV1Module(): Output: Dictionary to be rendered """ self.update_task_states() - return {'success': True, 'html': self.get_html_nonsystem(), 'allow_reset': self.allow_reset} + return {'success': True, 'html': self.get_html_nonsystem(), 'allow_reset': self.ready_to_reset} def reset(self, get): """ @@ -633,7 +633,7 @@ class CombinedOpenEndedV1Module(): Output: AJAX dictionary to tbe rendered """ if self.state != self.DONE: - if not self.allow_reset: + if not self.ready_to_reset: return self.out_of_sync_error(get) if self.student_attempts > self.attempts: @@ -645,14 +645,14 @@ class CombinedOpenEndedV1Module(): self.student_attempts, self.attempts) } self.state = self.INITIAL - self.allow_reset = False + self.ready_to_reset = False for i in xrange(0, len(self.task_xml)): self.current_task_number = i self.setup_next_task(reset=True) self.current_task.reset(self.system) self.task_states[self.current_task_number] = self.current_task.get_instance_state() self.current_task_number = 0 - self.allow_reset = False + self.ready_to_reset = False self.setup_next_task() return {'success': True, 'html': self.get_html_nonsystem()} @@ -669,7 +669,7 @@ class CombinedOpenEndedV1Module(): 'state': self.state, 'task_states': self.task_states, 'student_attempts': self.student_attempts, - 'ready_to_reset': self.allow_reset, + 'ready_to_reset': self.ready_to_reset, } return json.dumps(state) @@ -703,7 +703,7 @@ class CombinedOpenEndedV1Module(): entirely, in which case they will be in the self.DONE state), and if it is scored or not. @return: Boolean corresponding to the above. """ - return (self.state == self.DONE or self.allow_reset) and self.is_scored + return (self.state == self.DONE or self.ready_to_reset) and self.is_scored def get_score(self): """ From d20fc439ecfb310724d02ce37ecf9df86ebf363b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 11:47:48 -0500 Subject: [PATCH 09/12] Fix peer grading boolean parsing --- .../combined_open_ended_modulev1.py | 2 +- .../xmodule/xmodule/peer_grading_module.py | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 7b768457ba..c94f5abd80 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -138,7 +138,7 @@ class CombinedOpenEndedV1Module(): self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS) self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT - self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) + self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT display_due_date_string = self.instance_state.get('due', None) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index e6651ed85e..005bd015b5 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -58,16 +58,19 @@ class PeerGradingModule(XModule): else: self.peer_gs = MockPeerGradingService() - if self.use_for_single_location == True: + log.debug(self.use_for_single_location) + log.debug(type(self.use_for_single_location)) + log.debug(self.use_for_single_location==True) + if self.use_for_single_location in TRUE_DICT: try: self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) except: log.error("Linked location {0} for peer grading module {1} does not exist".format( self.link_to_location, self.location)) raise - due_date = self.linked_problem.metadata.get('peer_grading_due', None) + due_date = self.linked_problem._model_data.get('peer_grading_due', None) if due_date: - self.metadata['due'] = due_date + self._model_data['due'] = due_date try: self.timeinfo = TimeInfo(self.display_due_date_string, self.grace_period_string) @@ -120,7 +123,7 @@ class PeerGradingModule(XModule): """ if self.closed(): return self.peer_grading_closed() - if not self.use_for_single_location: + if self.use_for_single_location not in TRUE_DICT: return self.peer_grading() else: return self.peer_grading_problem({'location': self.link_to_location})['html'] @@ -171,7 +174,7 @@ class PeerGradingModule(XModule): pass def get_score(self): - if not self.use_for_single_location or not self.is_graded: + if self.use_for_single_location not in TRUE_DICT or self.is_graded not in TRUE_DICT: return None try: @@ -204,7 +207,7 @@ class PeerGradingModule(XModule): randomization, and 5/7 on another ''' max_grade = None - if self.use_for_single_location and self.is_graded: + if self.use_for_single_location in TRUE_DICT and self.is_graded in TRUE_DICT: max_grade = self.max_grade return max_grade @@ -450,7 +453,6 @@ class PeerGradingModule(XModule): error_text = problem_list_dict['error'] problem_list = problem_list_dict['problem_list'] - except GradingServiceError: #This is a student_facing_error error_text = EXTERNAL_GRADER_NO_CONTACT_ERROR @@ -480,8 +482,9 @@ class PeerGradingModule(XModule): problem_location = problem['location'] descriptor = _find_corresponding_module_for_location(problem_location) if descriptor: - problem['due'] = descriptor.metadata.get('peer_grading_due', None) - grace_period_string = descriptor.metadata.get('graceperiod', None) + log.debug(descriptor.__dict__) + problem['due'] = descriptor._model_data.get('peer_grading_due', None) + grace_period_string = descriptor._model_data.get('graceperiod', None) try: problem_timeinfo = TimeInfo(problem['due'], grace_period_string) except: @@ -516,7 +519,7 @@ class PeerGradingModule(XModule): Show individual problem interface ''' if get is None or get.get('location') is None: - if not self.use_for_single_location: + if self.use_for_single_location not in TRUE_DICT: #This is an error case, because it must be set to use a single location to be called without get parameters #This is a dev_facing_error log.error("Peer grading problem in peer_grading_module called with no get parameters, but use_for_single_location is False.") From f73979d03b8ea04f02f035b22d080a1edf6022e6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 11:57:14 -0500 Subject: [PATCH 10/12] Generate less db inserts --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4f806d6b78..ee1f42e585 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -182,7 +182,9 @@ class CombinedOpenEndedModule(XModule): def save_instance_data(self): for attribute in self.student_attributes: - setattr(self,attribute, getattr(self.child_module,attribute)) + child_attr = getattr(self.child_module,attribute) + if child_attr != getattr(self, attribute): + setattr(self,attribute, getattr(self.child_module,attribute)) class CombinedOpenEndedDescriptor(RawDescriptor): From c1218e8c084047547abbf41c477564a0c9c65a9e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 12:26:23 -0500 Subject: [PATCH 11/12] Remove debug statements --- common/lib/xmodule/xmodule/peer_grading_module.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 005bd015b5..5fc325b632 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -57,10 +57,7 @@ class PeerGradingModule(XModule): self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system) else: self.peer_gs = MockPeerGradingService() - - log.debug(self.use_for_single_location) - log.debug(type(self.use_for_single_location)) - log.debug(self.use_for_single_location==True) + if self.use_for_single_location in TRUE_DICT: try: self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) From b75b091581e8ab172a5154d649a0d0506472109d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 6 Mar 2013 12:27:22 -0500 Subject: [PATCH 12/12] Remove 2 more debug statements --- .../xmodule/open_ended_grading_classes/open_ended_module.py | 1 - common/lib/xmodule/xmodule/peer_grading_module.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py index a7b3f8f9ed..022509b659 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py @@ -579,7 +579,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'check_for_score': self.check_for_score, } - log.debug(dispatch) if dispatch not in handlers: #This is a dev_facing_error log.error("Cannot find {0} in handlers in handle_ajax function for open_ended_module.py".format(dispatch)) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 5fc325b632..2208d074a2 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -57,7 +57,7 @@ class PeerGradingModule(XModule): self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system) else: self.peer_gs = MockPeerGradingService() - + if self.use_for_single_location in TRUE_DICT: try: self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) @@ -479,7 +479,6 @@ class PeerGradingModule(XModule): problem_location = problem['location'] descriptor = _find_corresponding_module_for_location(problem_location) if descriptor: - log.debug(descriptor.__dict__) problem['due'] = descriptor._model_data.get('peer_grading_due', None) grace_period_string = descriptor._model_data.get('graceperiod', None) try: