From 60f9143fa5c577a2136d7e027a252be9898dac39 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 12 Dec 2012 14:04:04 -0500 Subject: [PATCH 001/171] Minor change --- common/lib/xmodule/xmodule/capa_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index d65fa1f40a..08f503f127 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -445,6 +445,7 @@ class CapaModule(XModule): return False + def update_score(self, get): """ Delivers grading response (e.g. from asynchronous code checking) to From 5858ec5e0d2a6d7e48ce0f2de6f87461c1c0e892 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 10:18:21 -0500 Subject: [PATCH 002/171] Begin making integrated module --- .../xmodule/combined_open_ended_module.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 common/lib/xmodule/xmodule/combined_open_ended_module.py diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py new file mode 100644 index 0000000000..044026311f --- /dev/null +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -0,0 +1,90 @@ +import copy +from fs.errors import ResourceNotFoundError +import itertools +import json +import logging +from lxml import etree +from lxml.html import rewrite_links +from path import path +import os +import sys + +from pkg_resources import resource_string + +from .capa_module import only_one, ComplexEncoder +from .editing_module import EditingDescriptor +from .html_checker import check_html +from progress import Progress +from .stringify import stringify_children +from .x_module import XModule +from .xml_module import XmlDescriptor +from xmodule.modulestore import Location +import self_assessment_module + +log = logging.getLogger("mitx.courseware") + +# Set the default number of max attempts. Should be 1 for production +# Set higher for debugging/testing +# attempts specified in xml definition overrides this. +MAX_ATTEMPTS = 1 + +# Set maximum available number of points. +# Overriden by max_score specified in xml. +MAX_SCORE = 1 + +class CombinedOpenEndedModule(XModule): + pass + +class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): + """ + Module for adding self assessment questions to courses + """ + mako_template = "widgets/html-edit.html" + module_class = CombinedOpenEndedModule + filename_extension = "xml" + + stores_state = True + has_score = True + template_dir_name = "combinedopenended" + + 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 rubric, prompt, and submitmessage into a dictionary. + + Returns: + { + 'rubric': 'some-html', + 'prompt': 'some-html', + 'submitmessage': 'some-html' + 'hintprompt': 'some-html' + } + """ + expected_children = ['task'] + for child in expected_children: + if len(xml_object.xpath(child)) == 0 : + raise ValueError("Combined Open Ended definition must include at least one '{0}' tag".format(child)) + + def parse(k): + """Assumes that xml_object has child k""" + return stringify_children(xml_object.xpath(k)[0]) + + return {'task': parse('task')} + + + def definition_to_xml(self, resource_fs): + '''Return an xml element representing this definition.''' + elt = etree.Element('selfassessment') + + def add_child(k): + child_str = '<{tag}>{body}'.format(tag=k, body=self.definition[k]) + child_node = etree.fromstring(child_str) + elt.append(child_node) + + for child in ['task']: + add_child(child) + + return elt \ No newline at end of file From e0d12bcbfe426bc34ad4992d82ab0b1939dce2cd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 10:21:04 -0500 Subject: [PATCH 003/171] Add in some descriptions --- .../xmodule/combined_open_ended_module.py | 57 ++++++++++++++++++- 1 file changed, 56 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 044026311f..c85552d442 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -33,7 +33,62 @@ MAX_ATTEMPTS = 1 MAX_SCORE = 1 class CombinedOpenEndedModule(XModule): - pass + STATE_VERSION = 1 + + # states + INITIAL = 'initial' + ASSESSING = 'assessing' + DONE = 'done' + TASK_TYPES=["self", "ml", "instructor", "peer"] + + js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} + js_module_name = "SelfAssessment" + + def __init__(self, system, location, definition, descriptor, + instance_state=None, shared_state=None, **kwargs): + XModule.__init__(self, system, location, definition, descriptor, + instance_state, shared_state, **kwargs) + + """ + Definition file should have multiple task blocks: + + Sample file: + + + + + + + + """ + + # Load instance state + if instance_state is not None: + instance_state = json.loads(instance_state) + else: + instance_state = {} + + instance_state = self.convert_state_to_current_format(instance_state) + + # History is a list of tuples of (answer, score, hint), where hint may be + # None for any element, and score and hint can be None for the last (current) + # element. + # Scores are on scale from 0 to max_score + self.history = instance_state.get('history', []) + + self.state = instance_state.get('state', 'initial') + + self.attempts = instance_state.get('attempts', 0) + self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) + + # Used for progress / grading. Currently get credit just for + # completion (doesn't matter if you self-assessed correct/incorrect). + self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) + + self.rubric = definition['rubric'] + self.prompt = definition['prompt'] + self.submit_message = definition['submitmessage'] + self.hint_prompt = definition['hintprompt'] class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ From 0be51cfa2d8e70a011e852c5a5a85b018be0cc95 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 10:52:18 -0500 Subject: [PATCH 004/171] Xmodule combined now working --- common/lib/xmodule/setup.py | 1 + .../xmodule/combined_open_ended_module.py | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index d3889bc388..34e4e658c9 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -19,6 +19,7 @@ setup( "abtest = xmodule.abtest_module:ABTestDescriptor", "book = xmodule.backcompat_module:TranslateCustomTagDescriptor", "chapter = xmodule.seq_module:SequenceDescriptor", + "combinedopenended = xmodule.combined_open_ended_module:CombinedOpenEndedDescriptor" "course = xmodule.course_module:CourseDescriptor", "customtag = xmodule.template_module:CustomTagDescriptor", "discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor", diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index c85552d442..cbbfe156c3 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -42,7 +42,7 @@ class CombinedOpenEndedModule(XModule): TASK_TYPES=["self", "ml", "instructor", "peer"] js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} - js_module_name = "SelfAssessment" + js_module_name = "CombinedOpenEnded" def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): @@ -54,7 +54,7 @@ class CombinedOpenEndedModule(XModule): Sample file: - + @@ -68,15 +68,14 @@ class CombinedOpenEndedModule(XModule): else: instance_state = {} - instance_state = self.convert_state_to_current_format(instance_state) - # History is a list of tuples of (answer, score, hint), where hint may be # 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.current_task = instance_state.get('current_task', 0) self.state = instance_state.get('state', 'initial') + self.problems = instance_state.get('problems', []) self.attempts = instance_state.get('attempts', 0) self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) @@ -85,10 +84,16 @@ class CombinedOpenEndedModule(XModule): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) - self.rubric = definition['rubric'] - self.prompt = definition['prompt'] - self.submit_message = definition['submitmessage'] - self.hint_prompt = definition['hintprompt'] + self.tasks=definition['tasks'] + log.debug(self.tasks) + + def setup_next_task(self): + pass + + def get_html(self): + html = "" + + return rewrite_links(html, self.rewrite_content_links) class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ @@ -125,9 +130,9 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): def parse(k): """Assumes that xml_object has child k""" - return stringify_children(xml_object.xpath(k)[0]) + return [stringify_children(xml_object.xpath(k)[i]) for i in xrange(0,len(xml_object.xpath(k)))] - return {'task': parse('task')} + return {'tasks': parse('task')} def definition_to_xml(self, resource_fs): From 867deae6e36f6bff9a4229a4d549777b52cddccf Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 11:20:45 -0500 Subject: [PATCH 005/171] Bugfixes --- .../xmodule/combined_open_ended_module.py | 31 ++++++++++++++----- 1 file changed, 24 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 cbbfe156c3..c5714ecc84 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -38,6 +38,7 @@ class CombinedOpenEndedModule(XModule): # states INITIAL = 'initial' ASSESSING = 'assessing' + INTERMEDIATE_DONE='intermediate_done' DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] @@ -72,7 +73,8 @@ class CombinedOpenEndedModule(XModule): # 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.current_task = instance_state.get('current_task', 0) + self.current_task_number = instance_state.get('current_task_number', 0) + self.tasks = instance_state.get('tasks', []) self.state = instance_state.get('state', 'initial') self.problems = instance_state.get('problems', []) @@ -84,16 +86,31 @@ class CombinedOpenEndedModule(XModule): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) - self.tasks=definition['tasks'] - log.debug(self.tasks) + self.task_xml=definition['task_xml'] + self.setup_next_task() + + def get_tag_name(self, xml): + tag=etree.fromstring(xml).tag + return tag def setup_next_task(self): - pass + if self.state in [self.ASSESSING, self.DONE]: + self.current_task=self.tasks[len(self.tasks)-1] + return True + + self.current_task_xml=self.task_xml[self.current_task_number] + current_task_type=self.get_tag_name(self.current_task_xml) + if current_task_type=="selfassessment": + self.current_task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) + self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(self.current_task_xml,self.system) + self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + return True def get_html(self): - html = "" + return self.current_task.get_html() - return rewrite_links(html, self.rewrite_content_links) + def handle_ajax(self, dispatch, get): + return self.current_task.handle_ajax(dispatch,get) class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ @@ -132,7 +149,7 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """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)))] - return {'tasks': parse('task')} + return {'task_xml': parse('task')} def definition_to_xml(self, resource_fs): From 4047106e48afb8a49664e7e85d3e8c26f34b7ac7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 11:38:55 -0500 Subject: [PATCH 006/171] Some progress with rendering --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 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 c5714ecc84..b8ad5fdaaa 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -102,7 +102,7 @@ class CombinedOpenEndedModule(XModule): current_task_type=self.get_tag_name(self.current_task_xml) if current_task_type=="selfassessment": self.current_task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) - self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(self.current_task_xml,self.system) + self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) return True From e975174d662bc18e86528a66afa9ad7fba1e7289 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 11:49:33 -0500 Subject: [PATCH 007/171] Convert self-assessment away from xmodule --- common/lib/xmodule/setup.py | 1 - .../xmodule/combined_open_ended_module.py | 3 ++- .../xmodule/xmodule/self_assessment_module.py | 18 +++++++----------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 34e4e658c9..c20b50ec66 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -29,7 +29,6 @@ setup( "problem = xmodule.capa_module:CapaDescriptor", "problemset = xmodule.seq_module:SequenceDescriptor", "section = xmodule.backcompat_module:SemanticSectionDescriptor", - "selfassessment = xmodule.self_assessment_module:SelfAssessmentDescriptor", "sequential = xmodule.seq_module:SequenceDescriptor", "slides = xmodule.backcompat_module:TranslateCustomTagDescriptor", "vertical = xmodule.vertical_module:VerticalDescriptor", diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index b8ad5fdaaa..b1585f9c10 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -107,7 +107,8 @@ class CombinedOpenEndedModule(XModule): return True def get_html(self): - return self.current_task.get_html() + html = self.current_task.get_html(self.system) + return rewrite_links(html, self.rewrite_content_links) def handle_ajax(self, dispatch, get): return self.current_task.handle_ajax(dispatch,get) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index eb8a275d35..eef81182ea 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -38,7 +38,7 @@ MAX_ATTEMPTS = 1 # Overriden by max_score specified in xml. MAX_SCORE = 1 -class SelfAssessmentModule(XModule): +class SelfAssessmentModule(): """ States: @@ -66,9 +66,6 @@ class SelfAssessmentModule(XModule): def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): - XModule.__init__(self, system, location, definition, descriptor, - instance_state, shared_state, **kwargs) - """ Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, and two optional attributes: @@ -116,11 +113,11 @@ class SelfAssessmentModule(XModule): self.state = instance_state.get('state', 'initial') self.attempts = instance_state.get('attempts', 0) - self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) + self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) + self._max_score = int(instance_state.get('max_score', MAX_SCORE)) self.rubric = definition['rubric'] self.prompt = definition['prompt'] @@ -224,7 +221,7 @@ class SelfAssessmentModule(XModule): """Can the module be reset?""" return self.state == self.DONE and self.attempts < self.max_attempts - def get_html(self): + def get_html(self, system): #set context variables and render template if self.state != self.INITIAL: latest = self.latest_answer() @@ -235,17 +232,16 @@ class SelfAssessmentModule(XModule): context = { 'prompt': self.prompt, 'previous_answer': previous_answer, - 'ajax_url': self.system.ajax_url, + 'ajax_url': system.ajax_url, 'initial_rubric': self.get_rubric_html(), 'initial_hint': self.get_hint_html(), 'initial_message': self.get_message_html(), 'state': self.state, 'allow_reset': self._allow_reset(), } - html = self.system.render_template('self_assessment_prompt.html', context) - # cdodge: perform link substitutions for any references to course static content (e.g. images) - return rewrite_links(html, self.rewrite_content_links) + html = system.render_template('self_assessment_prompt.html', context) + return html def max_score(self): """ From cd764d3d7c6c2bbf209254427469ecfe9bc3632c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 11:55:12 -0500 Subject: [PATCH 008/171] Modify self-assessment to not be an xmodule --- .../xmodule/combined_open_ended_module.py | 4 +-- .../xmodule/xmodule/self_assessment_module.py | 27 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index b1585f9c10..6e3fdc1ddd 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -43,7 +43,7 @@ class CombinedOpenEndedModule(XModule): TASK_TYPES=["self", "ml", "instructor", "peer"] js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} - js_module_name = "CombinedOpenEnded" + js_module_name = "SelfAssessment" def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): @@ -111,7 +111,7 @@ class CombinedOpenEndedModule(XModule): return rewrite_links(html, self.rewrite_content_links) def handle_ajax(self, dispatch, get): - return self.current_task.handle_ajax(dispatch,get) + return self.current_task.handle_ajax(dispatch,get, self.system) class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index eef81182ea..150ad571b7 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -233,8 +233,8 @@ class SelfAssessmentModule(): 'prompt': self.prompt, 'previous_answer': previous_answer, 'ajax_url': system.ajax_url, - 'initial_rubric': self.get_rubric_html(), - 'initial_hint': self.get_hint_html(), + 'initial_rubric': self.get_rubric_html(system), + 'initial_hint': self.get_hint_html(system), 'initial_message': self.get_message_html(), 'state': self.state, 'allow_reset': self._allow_reset(), @@ -270,7 +270,7 @@ class SelfAssessmentModule(): return None - def handle_ajax(self, dispatch, get): + def handle_ajax(self, dispatch, get, system): """ This is called by courseware.module_render, to handle an AJAX call. "get" is request.POST. @@ -292,7 +292,7 @@ class SelfAssessmentModule(): return 'Error' before = self.get_progress() - d = handlers[dispatch](get) + d = handlers[dispatch](get, system) after = self.get_progress() d.update({ 'progress_changed': after != before, @@ -309,7 +309,7 @@ class SelfAssessmentModule(): return {'success': False, 'error': 'The problem state got out-of-sync'} - def get_rubric_html(self): + def get_rubric_html(self,system): """ Return the appropriate version of the rubric, based on the state. """ @@ -328,9 +328,9 @@ class SelfAssessmentModule(): else: raise ValueError("Illegal state '%r'" % self.state) - return self.system.render_template('self_assessment_rubric.html', context) + return system.render_template('self_assessment_rubric.html', context) - def get_hint_html(self): + def get_hint_html(self,system): """ Return the appropriate version of the hint view, based on state. """ @@ -354,7 +354,7 @@ class SelfAssessmentModule(): else: raise ValueError("Illegal state '%r'" % self.state) - return self.system.render_template('self_assessment_hint.html', context) + return system.render_template('self_assessment_hint.html', context) def get_message_html(self): """ @@ -366,7 +366,7 @@ class SelfAssessmentModule(): return """
{0}
""".format(self.submit_message) - def save_answer(self, get): + def save_answer(self, get, system): """ After the answer is submitted, show the rubric. @@ -397,10 +397,10 @@ class SelfAssessmentModule(): return { 'success': True, - 'rubric_html': self.get_rubric_html() + 'rubric_html': self.get_rubric_html(system) } - def save_assessment(self, get): + def save_assessment(self, get, system): """ Save the assessment. If the student said they're right, don't ask for a hint, and go straight to the done state. Otherwise, do ask for a hint. @@ -433,13 +433,13 @@ class SelfAssessmentModule(): d['allow_reset'] = self._allow_reset() else: self.change_state(self.REQUEST_HINT) - d['hint_html'] = self.get_hint_html() + d['hint_html'] = self.get_hint_html(system) d['state'] = self.state return d - def save_hint(self, get): + def save_hint(self, get, system): ''' Save the hint. Returns a dict { 'success': bool, @@ -465,7 +465,6 @@ class SelfAssessmentModule(): 'history': self.history, } } - self.system.track_function('save_hint', event_info) return {'success': True, 'message_html': self.get_message_html(), From 517c79e00a1110df073d4dbb60c5167d3f7b9edc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 12:25:10 -0500 Subject: [PATCH 009/171] Fixing state tracking --- .../xmodule/combined_open_ended_module.py | 40 +++++++++++++++---- 1 file changed, 33 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 6e3fdc1ddd..67ba54fcc5 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -74,10 +74,9 @@ class CombinedOpenEndedModule(XModule): # element. # Scores are on scale from 0 to max_score self.current_task_number = instance_state.get('current_task_number', 0) - self.tasks = instance_state.get('tasks', []) + self.task_states= instance_state.get('task_states', []) self.state = instance_state.get('state', 'initial') - self.problems = instance_state.get('problems', []) self.attempts = instance_state.get('attempts', 0) self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) @@ -94,24 +93,51 @@ class CombinedOpenEndedModule(XModule): return tag def setup_next_task(self): + current_task_state=None if self.state in [self.ASSESSING, self.DONE]: - self.current_task=self.tasks[len(self.tasks)-1] - return True + current_task_state=self.task_states[len(self.task_states)-1] + + log.debug(self.task_states) self.current_task_xml=self.task_xml[self.current_task_number] current_task_type=self.get_tag_name(self.current_task_xml) if current_task_type=="selfassessment": self.current_task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) - self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + if current_task_state is None: + self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING + else: + self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + return True def get_html(self): html = self.current_task.get_html(self.system) - return rewrite_links(html, self.rewrite_content_links) + return_html = rewrite_links(html, self.rewrite_content_links) + self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + return return_html def handle_ajax(self, dispatch, get): - return self.current_task.handle_ajax(dispatch,get, self.system) + return_html = self.current_task.handle_ajax(dispatch,get, self.system) + self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + return return_html + + def get_instance_state(self): + """ + Get the current score and state + """ + + state = { + 'version': self.STATE_VERSION, + 'current_task_number': self.current_task_number, + 'state': self.state, + 'task_states': self.task_states, + 'attempts': self.attempts, + } + + return json.dumps(state) class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ From b009d15dcc7e4f816348fde52fbba2238fbf534e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Dec 2012 12:28:31 -0500 Subject: [PATCH 010/171] Self assessment wrapped into another module now works --- common/lib/xmodule/xmodule/self_assessment_module.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 150ad571b7..0c37384c16 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -457,21 +457,12 @@ class SelfAssessmentModule(): self.record_latest_hint(get['hint']) self.change_state(self.DONE) - # To the tracking logs! - event_info = { - 'selfassessment_id': self.location.url(), - 'state': { - 'version': self.STATE_VERSION, - 'history': self.history, - } - } - return {'success': True, 'message_html': self.get_message_html(), 'allow_reset': self._allow_reset()} - def reset(self, get): + def reset(self, get, system): """ If resetting is allowed, reset the state. From 2bbbe12ae8d58f6c3a476b4af5deefc44fca2a5d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 2 Jan 2013 13:13:05 -0500 Subject: [PATCH 011/171] Refactor instance state saving --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 7 +++++-- common/lib/xmodule/xmodule/open_ended_module.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 common/lib/xmodule/xmodule/open_ended_module.py diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 67ba54fcc5..aa759ea7dc 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -116,12 +116,15 @@ class CombinedOpenEndedModule(XModule): def get_html(self): html = self.current_task.get_html(self.system) return_html = rewrite_links(html, self.rewrite_content_links) - self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + self.update_task_states() return return_html + def update_task_states(self): + self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + def handle_ajax(self, dispatch, get): return_html = self.current_task.handle_ajax(dispatch,get, self.system) - self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + self.update_task_states() return return_html def get_instance_state(self): diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -0,0 +1 @@ + From ae0623ab9ba08c7a28f51056fbe14fd917522291 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 2 Jan 2013 13:18:30 -0500 Subject: [PATCH 012/171] Working on transitions between states --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index aa759ea7dc..04f848f306 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -121,6 +121,16 @@ class CombinedOpenEndedModule(XModule): def update_task_states(self): self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() + current_task_state=json.loads(self.task_states[len(self.task_states)-1]) + if current_task_state['state']==self.DONE: + self.current_task_number+=1 + if self.current_task_number==len(self.task_xml): + self.state=self.DONE + self.current_task_number=self.current_task_number-1 + else: + self.state=self.INTERMEDIATE_DONE + self.setup_next_task() + def handle_ajax(self, dispatch, get): return_html = self.current_task.handle_ajax(dispatch,get, self.system) From 4ea9c738a352d7701d717545412d7c8de1312065 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 14:24:23 -0500 Subject: [PATCH 013/171] Fix state transitions --- .../xmodule/xmodule/combined_open_ended_module.py | 13 ++++++++++--- 1 file changed, 10 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 04f848f306..50d1a7df0c 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -114,12 +114,13 @@ class CombinedOpenEndedModule(XModule): return True def get_html(self): + self.update_task_states() html = self.current_task.get_html(self.system) return_html = rewrite_links(html, self.rewrite_content_links) - self.update_task_states() return return_html def update_task_states(self): + changed=False self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() current_task_state=json.loads(self.task_states[len(self.task_states)-1]) if current_task_state['state']==self.DONE: @@ -129,13 +130,19 @@ class CombinedOpenEndedModule(XModule): self.current_task_number=self.current_task_number-1 else: self.state=self.INTERMEDIATE_DONE + changed=True self.setup_next_task() + return changed + def update_task_states_ajax(self,return_html): + changed=self.update_task_states() + if changed(): + return_html=self.get_html() + return return_html def handle_ajax(self, dispatch, get): return_html = self.current_task.handle_ajax(dispatch,get, self.system) - self.update_task_states() - return return_html + return self.update_task_states_ajax(return_html) def get_instance_state(self): """ From bc6838f8dac0cf628095065920c625205bbba3b7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 16:25:39 -0500 Subject: [PATCH 014/171] Minor changes --- .../xmodule/combined_open_ended_module.py | 30 +++++++++++++++++-- .../js/src/combinedopenended/display.coffee | 0 .../xmodule/js/src/openended/display.coffee | 0 .../js/src/selfassessment/display.coffee | 2 -- lms/templates/self_assessment_prompt.html | 1 - 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee create mode 100644 common/lib/xmodule/xmodule/js/src/openended/display.coffee diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 50d1a7df0c..a314c89f01 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -42,7 +42,7 @@ class CombinedOpenEndedModule(XModule): DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} + js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee'), resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/openended/display.coffee')]} js_module_name = "SelfAssessment" def __init__(self, system, location, definition, descriptor, @@ -92,6 +92,7 @@ class CombinedOpenEndedModule(XModule): tag=etree.fromstring(xml).tag return tag + def setup_next_task(self): current_task_state=None if self.state in [self.ASSESSING, self.DONE]: @@ -104,10 +105,18 @@ class CombinedOpenEndedModule(XModule): if current_task_type=="selfassessment": self.current_task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) - if current_task_state is None: + if current_task_state is None and self.current_task_number==0: self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING + elif current_task_state is None and self.current_task_number>0: + last_response=self.get_last_response(self.current_task_number-1) + current_task_state = ('{"state": "assessing", "version": 1, "max_score": {max_score}, ' + '"attempts": 0, "history": [{"answer": "{answer}"}]}' + .format(max_score=self._max_score, answer=last_response)) + self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING else: self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) @@ -119,6 +128,21 @@ class CombinedOpenEndedModule(XModule): return_html = rewrite_links(html, self.rewrite_content_links) return return_html + def get_last_response(self, task_number): + last_response="" + task_state = self.task_states[task_number] + task_xml=self.task_xml[task_number] + task_type=self.get_tag_name(task_xml) + + if task_type=="selfassessment": + task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) + task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) + task=self_assessment_module.SelfAssessmentModule(self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) + last_response=task.latest_answer() + last_score = task.latest_score() + + return last_response, last_score + def update_task_states(self): changed=False self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() @@ -136,7 +160,7 @@ class CombinedOpenEndedModule(XModule): def update_task_states_ajax(self,return_html): changed=self.update_task_states() - if changed(): + if changed: return_html=self.get_html() return return_html diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/lib/xmodule/xmodule/js/src/openended/display.coffee b/common/lib/xmodule/xmodule/js/src/openended/display.coffee new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 5b70ab29aa..4bf9cc8180 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -15,8 +15,6 @@ class @SelfAssessment @hint_wrapper = @$('.hint-wrapper') @message_wrapper = @$('.message-wrapper') @submit_button = @$('.submit-button') - @reset_button = @$('.reset-button') - @reset_button.click @reset @find_assessment_elements() @find_hint_elements() diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 91472cbdaf..e54864c988 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -16,5 +16,4 @@
${initial_message}
- From cac5b75cf4b6e570b9d94ba20480bb494a9b5d31 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 16:36:18 -0500 Subject: [PATCH 015/171] Change modules --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 3 ++- 1 file changed, 2 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 a314c89f01..46f912a8de 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -42,7 +42,8 @@ class CombinedOpenEndedModule(XModule): DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee'), resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/openended/display.coffee')]} + #, resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/openended/display.coffee') + js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} js_module_name = "SelfAssessment" def __init__(self, system, location, definition, descriptor, From 8e03c8f6557c2c50db18743dc82b5986c47ce396 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 16:50:47 -0500 Subject: [PATCH 016/171] Fix missing comma issue --- common/lib/xmodule/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index c20b50ec66..c867fca228 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -19,7 +19,7 @@ setup( "abtest = xmodule.abtest_module:ABTestDescriptor", "book = xmodule.backcompat_module:TranslateCustomTagDescriptor", "chapter = xmodule.seq_module:SequenceDescriptor", - "combinedopenended = xmodule.combined_open_ended_module:CombinedOpenEndedDescriptor" + "combinedopenended = xmodule.combined_open_ended_module:CombinedOpenEndedDescriptor", "course = xmodule.course_module:CourseDescriptor", "customtag = xmodule.template_module:CustomTagDescriptor", "discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor", From 8ca12ce50023cf1f1525b100465cf43fcfdb59d8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 17:12:58 -0500 Subject: [PATCH 017/171] Make combined open ended a container --- .../xmodule/combined_open_ended_module.py | 14 +++++++++++ .../combinedopenended.coffee | 23 +++++++++++++++++++ .../js/src/combinedopenended/display.coffee | 0 .../xmodule/js/src/openended/display.coffee | 0 .../lib/xmodule/xmodule/open_ended_module.py | 1 - lms/templates/combined_open_ended.html | 9 ++++++++ 6 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee delete mode 100644 common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee delete mode 100644 common/lib/xmodule/xmodule/js/src/openended/display.coffee delete mode 100644 common/lib/xmodule/xmodule/open_ended_module.py create mode 100644 lms/templates/combined_open_ended.html diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 46f912a8de..5eec8f34fd 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -124,6 +124,20 @@ class CombinedOpenEndedModule(XModule): return True def get_html(self): + task_html=self.get_html_base() + #set context variables and render template + + context = { + 'items': [{'content' : task_html}], + 'ajax_url': self.system.ajax_url, + 'allow_reset': True, + } + + html = system.render_template('combined_open_ended.html', context) + return html + + + def get_html_base(self): self.update_task_states() html = self.current_task.get_html(self.system) return_html = rewrite_links(html, self.rewrite_content_links) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee new file mode 100644 index 0000000000..6e286544a5 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee @@ -0,0 +1,23 @@ +class @CombinedOpenEnded + constructor: (element) -> + @el = $(element).find('section.combined-open-ended') + @ajax_url = @el.data('ajax-url') + @reset_button = @$('.reset-button') + @reset_button.click @reset + + reset: (event) => + event.preventDefault() + if @state == 'done' + $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => + if response.success + @answer_area.val('') + @rubric_wrapper.html('') + @hint_wrapper.html('') + @message_wrapper.html('') + @state = 'initial' + @rebind() + @reset_button.hide() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/lib/xmodule/xmodule/js/src/openended/display.coffee b/common/lib/xmodule/xmodule/js/src/openended/display.coffee deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py deleted file mode 100644 index 8b13789179..0000000000 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html new file mode 100644 index 0000000000..3d4a69e0be --- /dev/null +++ b/lms/templates/combined_open_ended.html @@ -0,0 +1,9 @@ +
+ + % for item in items: +
${item['content'] | h}
+ % endfor + + +
+ From dc0a798941553082fc3c311b6793f2598f5b089e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 17:19:13 -0500 Subject: [PATCH 018/171] Javascript fixes to remove reset button from self assessment --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 6 +++--- .../{combinedopenended.coffee => display.coffee} | 0 .../xmodule/xmodule/js/src/selfassessment/display.coffee | 5 ----- lms/templates/combined_open_ended.html | 2 +- 4 files changed, 4 insertions(+), 9 deletions(-) rename common/lib/xmodule/xmodule/js/src/combinedopenended/{combinedopenended.coffee => display.coffee} (100%) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 5eec8f34fd..82086d6b54 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -42,8 +42,8 @@ class CombinedOpenEndedModule(XModule): DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] - #, resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/openended/display.coffee') - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} + #, resource_string(__name__, 'js/src/openended/display.coffee') + js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee'), resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} js_module_name = "SelfAssessment" def __init__(self, system, location, definition, descriptor, @@ -133,7 +133,7 @@ class CombinedOpenEndedModule(XModule): 'allow_reset': True, } - html = system.render_template('combined_open_ended.html', context) + html = self.system.render_template('combined_open_ended.html', context) return html diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee similarity index 100% rename from common/lib/xmodule/xmodule/js/src/combinedopenended/combinedopenended.coffee rename to common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index 4bf9cc8180..ea1f02c101 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -29,7 +29,6 @@ class @SelfAssessment # rebind to the appropriate function for the current state @submit_button.unbind('click') @submit_button.show() - @reset_button.hide() @hint_area.attr('disabled', false) if @state == 'initial' @answer_area.attr("disabled", false) @@ -47,10 +46,6 @@ class @SelfAssessment @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() - if @allow_reset - @reset_button.show() - else - @reset_button.hide() find_assessment_elements: -> diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 3d4a69e0be..76c28b0e31 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,7 +1,7 @@
% for item in items: -
${item['content'] | h}
+
${item['content'] | n}
% endfor From 36c55d347bbc917f2ce787a9a541becb212fbb81 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 17:27:15 -0500 Subject: [PATCH 019/171] Combined JS --- .../xmodule/combined_open_ended_module.py | 4 +- .../js/src/combinedopenended/display.coffee | 137 ++++++++++++++++-- .../js/src/selfassessment/display.coffee | 123 ---------------- 3 files changed, 123 insertions(+), 141 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 82086d6b54..e77cd254e2 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -43,8 +43,8 @@ class CombinedOpenEndedModule(XModule): TASK_TYPES=["self", "ml", "instructor", "peer"] #, resource_string(__name__, 'js/src/openended/display.coffee') - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee'), resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} - js_module_name = "SelfAssessment" + js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} + js_module_name = "CombinedOpenEnded" def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 6e286544a5..897d5f1b67 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -1,23 +1,128 @@ class @CombinedOpenEnded constructor: (element) -> - @el = $(element).find('section.combined-open-ended') + @el = $(element).find('section.self-assessment') + @id = @el.data('id') @ajax_url = @el.data('ajax-url') + @state = @el.data('state') + @allow_reset = @el.data('allow_reset') + # valid states: 'initial', 'assessing', 'request_hint', 'done' + + # Where to put the rubric once we load it + @errors_area = @$('.error') + @answer_area = @$('textarea.answer') + + @rubric_wrapper = @$('.rubric-wrapper') + @hint_wrapper = @$('.hint-wrapper') + @message_wrapper = @$('.message-wrapper') + @submit_button = @$('.submit-button') + @reset_button = @$('.reset-button') @reset_button.click @reset + @find_assessment_elements() + @find_hint_elements() + + @rebind() + + # locally scoped jquery. + $: (selector) -> + $(selector, @el) + + rebind: () => + # rebind to the appropriate function for the current state + @submit_button.unbind('click') + @submit_button.show() + @hint_area.attr('disabled', false) + if @state == 'initial' + @answer_area.attr("disabled", false) + @submit_button.prop('value', 'Submit') + @submit_button.click @save_answer + else if @state == 'assessing' + @answer_area.attr("disabled", true) + @submit_button.prop('value', 'Submit assessment') + @submit_button.click @save_assessment + else if @state == 'request_hint' + @answer_area.attr("disabled", true) + @submit_button.prop('value', 'Submit hint') + @submit_button.click @save_hint + else if @state == 'done' + @answer_area.attr("disabled", true) + @hint_area.attr('disabled', true) + @submit_button.hide() + + find_assessment_elements: -> + @assessment = @$('select.assessment') + + find_hint_elements: -> + @hint_area = @$('textarea.hint') + + save_answer: (event) => + event.preventDefault() + if @state == 'initial' + data = {'student_answer' : @answer_area.val()} + $.postWithPrefix "#{@ajax_url}/save_answer", data, (response) => + if response.success + @rubric_wrapper.html(response.rubric_html) + @state = 'assessing' + @find_assessment_elements() + @rebind() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + save_assessment: (event) => + event.preventDefault() + if @state == 'assessing' + data = {'assessment' : @assessment.find(':selected').text()} + $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => + if response.success + @state = response.state + + if @state == 'request_hint' + @hint_wrapper.html(response.hint_html) + @find_hint_elements() + else if @state == 'done' + @message_wrapper.html(response.message_html) + @allow_reset = response.allow_reset + + @rebind() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + + save_hint: (event) => + event.preventDefault() + if @state == 'request_hint' + data = {'hint' : @hint_area.val()} + + $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => + if response.success + @message_wrapper.html(response.message_html) + @state = 'done' + @allow_reset = response.allow_reset + @rebind() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + reset: (event) => - event.preventDefault() - if @state == 'done' - $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => - if response.success - @answer_area.val('') - @rubric_wrapper.html('') - @hint_wrapper.html('') - @message_wrapper.html('') - @state = 'initial' - @rebind() - @reset_button.hide() - else - @errors_area.html(response.error) - else - @errors_area.html('Problem state got out of sync. Try reloading the page.') \ No newline at end of file + event.preventDefault() + if @state == 'done' + $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => + if response.success + @answer_area.val('') + @rubric_wrapper.html('') + @hint_wrapper.html('') + @message_wrapper.html('') + @state = 'initial' + @rebind() + @reset_button.hide() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee index ea1f02c101..30024478b1 100644 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee @@ -1,126 +1,3 @@ class @SelfAssessment constructor: (element) -> - @el = $(element).find('section.self-assessment') - @id = @el.data('id') - @ajax_url = @el.data('ajax-url') - @state = @el.data('state') - @allow_reset = @el.data('allow_reset') - # valid states: 'initial', 'assessing', 'request_hint', 'done' - # Where to put the rubric once we load it - @errors_area = @$('.error') - @answer_area = @$('textarea.answer') - - @rubric_wrapper = @$('.rubric-wrapper') - @hint_wrapper = @$('.hint-wrapper') - @message_wrapper = @$('.message-wrapper') - @submit_button = @$('.submit-button') - - @find_assessment_elements() - @find_hint_elements() - - @rebind() - - # locally scoped jquery. - $: (selector) -> - $(selector, @el) - - rebind: () => - # rebind to the appropriate function for the current state - @submit_button.unbind('click') - @submit_button.show() - @hint_area.attr('disabled', false) - if @state == 'initial' - @answer_area.attr("disabled", false) - @submit_button.prop('value', 'Submit') - @submit_button.click @save_answer - else if @state == 'assessing' - @answer_area.attr("disabled", true) - @submit_button.prop('value', 'Submit assessment') - @submit_button.click @save_assessment - else if @state == 'request_hint' - @answer_area.attr("disabled", true) - @submit_button.prop('value', 'Submit hint') - @submit_button.click @save_hint - else if @state == 'done' - @answer_area.attr("disabled", true) - @hint_area.attr('disabled', true) - @submit_button.hide() - - - find_assessment_elements: -> - @assessment = @$('select.assessment') - - find_hint_elements: -> - @hint_area = @$('textarea.hint') - - save_answer: (event) => - event.preventDefault() - if @state == 'initial' - data = {'student_answer' : @answer_area.val()} - $.postWithPrefix "#{@ajax_url}/save_answer", data, (response) => - if response.success - @rubric_wrapper.html(response.rubric_html) - @state = 'assessing' - @find_assessment_elements() - @rebind() - else - @errors_area.html(response.error) - else - @errors_area.html('Problem state got out of sync. Try reloading the page.') - - save_assessment: (event) => - event.preventDefault() - if @state == 'assessing' - data = {'assessment' : @assessment.find(':selected').text()} - $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => - if response.success - @state = response.state - - if @state == 'request_hint' - @hint_wrapper.html(response.hint_html) - @find_hint_elements() - else if @state == 'done' - @message_wrapper.html(response.message_html) - @allow_reset = response.allow_reset - - @rebind() - else - @errors_area.html(response.error) - else - @errors_area.html('Problem state got out of sync. Try reloading the page.') - - - save_hint: (event) => - event.preventDefault() - if @state == 'request_hint' - data = {'hint' : @hint_area.val()} - - $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => - if response.success - @message_wrapper.html(response.message_html) - @state = 'done' - @allow_reset = response.allow_reset - @rebind() - else - @errors_area.html(response.error) - else - @errors_area.html('Problem state got out of sync. Try reloading the page.') - - - reset: (event) => - event.preventDefault() - if @state == 'done' - $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => - if response.success - @answer_area.val('') - @rubric_wrapper.html('') - @hint_wrapper.html('') - @message_wrapper.html('') - @state = 'initial' - @rebind() - @reset_button.hide() - else - @errors_area.html(response.error) - else - @errors_area.html('Problem state got out of sync. Try reloading the page.') From b8d49511e1415d51270befef773e8f273c9f732b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 18:14:43 -0500 Subject: [PATCH 020/171] Fixing javascript display bugs --- .../xmodule/combined_open_ended_module.py | 5 +-- .../js/src/combinedopenended/display.coffee | 39 +++++++++++-------- .../js/src/selfassessment/display.coffee | 3 -- .../xmodule/xmodule/self_assessment_module.py | 3 -- lms/templates/combined_open_ended.html | 4 +- lms/templates/self_assessment_prompt.html | 2 +- 6 files changed, 28 insertions(+), 28 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index e77cd254e2..c75de86b02 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -112,9 +112,8 @@ class CombinedOpenEndedModule(XModule): self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: last_response=self.get_last_response(self.current_task_number-1) - current_task_state = ('{"state": "assessing", "version": 1, "max_score": {max_score}, ' - '"attempts": 0, "history": [{"answer": "{answer}"}]}' - .format(max_score=self._max_score, answer=last_response)) + current_task_state = ("{'state': 'assessing', 'version': 1, 'max_score': {" + str(self._max_score) + "}, " + + "'attempts': 0, 'history': [{'answer': '{" + str(last_response) + "}'}]}") self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 897d5f1b67..ebb440b996 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -1,13 +1,16 @@ class @CombinedOpenEnded constructor: (element) -> - @el = $(element).find('section.self-assessment') + @el = $(element).find('section.combined-open-ended') @id = @el.data('id') @ajax_url = @el.data('ajax-url') @state = @el.data('state') @allow_reset = @el.data('allow_reset') + @reset_button = @$('.reset-button') + @reset_button.click @reset # valid states: 'initial', 'assessing', 'request_hint', 'done' # Where to put the rubric once we load it + @el = $(element).find('section.open-ended-child') @errors_area = @$('.error') @answer_area = @$('textarea.answer') @@ -15,9 +18,7 @@ class @CombinedOpenEnded @hint_wrapper = @$('.hint-wrapper') @message_wrapper = @$('.message-wrapper') @submit_button = @$('.submit-button') - - @reset_button = @$('.reset-button') - @reset_button.click @reset + @child_state = @el.data('state') @find_assessment_elements() @find_hint_elements() @@ -32,23 +33,28 @@ class @CombinedOpenEnded # rebind to the appropriate function for the current state @submit_button.unbind('click') @submit_button.show() + @reset_button.hide() @hint_area.attr('disabled', false) - if @state == 'initial' + if @child_state == 'initial' @answer_area.attr("disabled", false) @submit_button.prop('value', 'Submit') @submit_button.click @save_answer - else if @state == 'assessing' + else if @child_state == 'assessing' @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit assessment') @submit_button.click @save_assessment - else if @state == 'request_hint' + else if @child_state == 'request_hint' @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit hint') @submit_button.click @save_hint - else if @state == 'done' + else if @child_state == 'done' @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() + if @allow_reset + @reset_button.show() + else + @reset_button.hide() find_assessment_elements: -> @assessment = @$('select.assessment') @@ -58,12 +64,12 @@ class @CombinedOpenEnded save_answer: (event) => event.preventDefault() - if @state == 'initial' + if @child_state == 'initial' data = {'student_answer' : @answer_area.val()} $.postWithPrefix "#{@ajax_url}/save_answer", data, (response) => if response.success @rubric_wrapper.html(response.rubric_html) - @state = 'assessing' + @child_state = 'assessing' @find_assessment_elements() @rebind() else @@ -73,16 +79,16 @@ class @CombinedOpenEnded save_assessment: (event) => event.preventDefault() - if @state == 'assessing' + if @child_state == 'assessing' data = {'assessment' : @assessment.find(':selected').text()} $.postWithPrefix "#{@ajax_url}/save_assessment", data, (response) => if response.success - @state = response.state + @child_state = response.state - if @state == 'request_hint' + if @child_state == 'request_hint' @hint_wrapper.html(response.hint_html) @find_hint_elements() - else if @state == 'done' + else if @child_state == 'done' @message_wrapper.html(response.message_html) @allow_reset = response.allow_reset @@ -95,13 +101,13 @@ class @CombinedOpenEnded save_hint: (event) => event.preventDefault() - if @state == 'request_hint' + if @child_state == 'request_hint' data = {'hint' : @hint_area.val()} $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => if response.success @message_wrapper.html(response.message_html) - @state = 'done' + @child_state = 'done' @allow_reset = response.allow_reset @rebind() else @@ -112,6 +118,7 @@ class @CombinedOpenEnded reset: (event) => event.preventDefault() + @errors_area.html('Problem state got out of sync. Try reloading the page.') if @state == 'done' $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => if response.success diff --git a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee b/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee deleted file mode 100644 index 30024478b1..0000000000 --- a/common/lib/xmodule/xmodule/js/src/selfassessment/display.coffee +++ /dev/null @@ -1,3 +0,0 @@ -class @SelfAssessment - constructor: (element) -> - diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 0c37384c16..a3b4b56e35 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -61,9 +61,6 @@ class SelfAssessmentModule(): REQUEST_HINT = 'request_hint' DONE = 'done' - js = {'coffee': [resource_string(__name__, 'js/src/selfassessment/display.coffee')]} - js_module_name = "SelfAssessment" - def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): """ diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 76c28b0e31..65a57b98eb 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,9 +1,9 @@ -
+
% for item in items:
${item['content'] | n}
% endfor -
+ diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index e54864c988..91223fdaa1 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -1,4 +1,4 @@ -
From beef00c13a4c30c04a0335b09935f0ea8b2121ae Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 18:31:19 -0500 Subject: [PATCH 021/171] Json instance state fix --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 7 ++++--- 1 file changed, 4 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 c75de86b02..14e04d399e 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -111,9 +111,10 @@ class CombinedOpenEndedModule(XModule): self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: - last_response=self.get_last_response(self.current_task_number-1) - current_task_state = ("{'state': 'assessing', 'version': 1, 'max_score': {" + str(self._max_score) + "}, " + - "'attempts': 0, 'history': [{'answer': '{" + str(last_response) + "}'}]}") + last_response, last_score=self.get_last_response(self.current_task_number-1) + current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + '"attempts": 0, "history": [{"answer": "' + str(last_response) + '"}]}') + {"state": "done", "version": 1, "max_score": 1, "attempts": 1, "history": [{"answer": "gdgddg", "score": 0, "hint": "dfdfdf"}]} self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING From 418c45f4d39a311bd8678e9b21b017f998c14896 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 18:34:33 -0500 Subject: [PATCH 022/171] Bugfixes --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index ebb440b996..1636804e71 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -119,7 +119,7 @@ class @CombinedOpenEnded reset: (event) => event.preventDefault() @errors_area.html('Problem state got out of sync. Try reloading the page.') - if @state == 'done' + if @child_state == 'done' $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => if response.success @answer_area.val('') From 5d6509dcb5933ff4fac2dbc944be2432a606d058 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 18:46:48 -0500 Subject: [PATCH 023/171] add in js variables for element wrappers --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 1636804e71..0c54e9af46 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -7,6 +7,7 @@ class @CombinedOpenEnded @allow_reset = @el.data('allow_reset') @reset_button = @$('.reset-button') @reset_button.click @reset + @combined_open_ended= @$('.combined-open-ended') # valid states: 'initial', 'assessing', 'request_hint', 'done' # Where to put the rubric once we load it @@ -20,6 +21,8 @@ class @CombinedOpenEnded @submit_button = @$('.submit-button') @child_state = @el.data('state') + @open_ended_child= @$('.open-ended-child') + @find_assessment_elements() @find_hint_elements() From 406d6c0c85e776b9c975c4ec772909129439a5a4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 3 Jan 2013 18:49:27 -0500 Subject: [PATCH 024/171] js name changes --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 0c54e9af46..031cc5183b 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -129,7 +129,7 @@ class @CombinedOpenEnded @rubric_wrapper.html('') @hint_wrapper.html('') @message_wrapper.html('') - @state = 'initial' + @child_state = 'initial' @rebind() @reset_button.hide() else From d5c3b58da394e068cc73af42204be5b7f4f749cd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 09:56:45 -0500 Subject: [PATCH 025/171] Change reset and next problem code --- .../xmodule/combined_open_ended_module.py | 29 +++++++++++++++++-- .../js/src/combinedopenended/display.coffee | 26 ++++++++++++++--- .../xmodule/xmodule/self_assessment_module.py | 2 +- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 14e04d399e..58d0cd4560 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -176,12 +176,35 @@ class CombinedOpenEndedModule(XModule): def update_task_states_ajax(self,return_html): changed=self.update_task_states() if changed: - return_html=self.get_html() + #return_html=self.get_html() + pass return return_html def handle_ajax(self, dispatch, get): - return_html = self.current_task.handle_ajax(dispatch,get, self.system) - return self.update_task_states_ajax(return_html) + """ + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress': 'none'/'in_progress'/'done', + } + """ + + handlers = { + 'next_problem': self.next_problem, + 'reset': self.reset, + } + + if dispatch not in handlers: + return_html = self.current_task.handle_ajax(dispatch,get, self.system) + return self.update_task_states_ajax(return_html) + + def next_problem(self): + pass + + def reset(self): + pass def get_instance_state(self): """ diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 031cc5183b..a7aee1b895 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -8,7 +8,7 @@ class @CombinedOpenEnded @reset_button = @$('.reset-button') @reset_button.click @reset @combined_open_ended= @$('.combined-open-ended') - # valid states: 'initial', 'assessing', 'request_hint', 'done' + # valid states: 'initial', 'assessing', 'post_assessment', 'done' # Where to put the rubric once we load it @el = $(element).find('section.open-ended-child') @@ -46,7 +46,7 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit assessment') @submit_button.click @save_assessment - else if @child_state == 'request_hint' + else if @child_state == 'post_assessment' @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit hint') @submit_button.click @save_hint @@ -88,7 +88,7 @@ class @CombinedOpenEnded if response.success @child_state = response.state - if @child_state == 'request_hint' + if @child_state == 'post_assessment' @hint_wrapper.html(response.hint_html) @find_hint_elements() else if @child_state == 'done' @@ -104,7 +104,7 @@ class @CombinedOpenEnded save_hint: (event) => event.preventDefault() - if @child_state == 'request_hint' + if @child_state == 'post_assessment' data = {'hint' : @hint_area.val()} $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => @@ -134,5 +134,23 @@ class @CombinedOpenEnded @reset_button.hide() else @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + next_problem (event) => + event.preventDefault() + @errors_area.html('Problem state got out of sync. Try reloading the page.') + if @child_state == 'done' + $.postWithPrefix "#{@ajax_url}/next_problem", {}, (response) => + if response.success + @answer_area.val('') + @rubric_wrapper.html('') + @hint_wrapper.html('') + @message_wrapper.html('') + @child_state = 'initial' + @rebind() + @reset_button.hide() + else + @errors_area.html(response.error) else @errors_area.html('Problem state got out of sync. Try reloading the page.') \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index a3b4b56e35..cba13c7e54 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -58,7 +58,7 @@ class SelfAssessmentModule(): # states INITIAL = 'initial' ASSESSING = 'assessing' - REQUEST_HINT = 'request_hint' + REQUEST_HINT = 'post_assessment' DONE = 'done' def __init__(self, system, location, definition, descriptor, From 91a9962bef454d5ef626d3680a515a42a88a498b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 09:59:25 -0500 Subject: [PATCH 026/171] Improve next problem handling and state tracking --- .../xmodule/js/src/combinedopenended/display.coffee | 13 +++++++++---- lms/templates/combined_open_ended.html | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index a7aee1b895..0e8cadd69f 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -7,6 +7,8 @@ class @CombinedOpenEnded @allow_reset = @el.data('allow_reset') @reset_button = @$('.reset-button') @reset_button.click @reset + @next_problem_button = @$('.next-step-button') + @next_problem_button.click @next_problem @combined_open_ended= @$('.combined-open-ended') # valid states: 'initial', 'assessing', 'post_assessment', 'done' @@ -37,6 +39,7 @@ class @CombinedOpenEnded @submit_button.unbind('click') @submit_button.show() @reset_button.hide() + @next_problem_button.hide() @hint_area.attr('disabled', false) if @child_state == 'initial' @answer_area.attr("disabled", false) @@ -54,10 +57,12 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() - if @allow_reset - @reset_button.show() - else - @reset_button.hide() + if !@state == 'done' + @next_problem_button.show() + if @allow_reset + @reset_button.show() + else + @reset_button.hide() find_assessment_elements: -> @assessment = @$('select.assessment') diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 65a57b98eb..51afddcd15 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -5,5 +5,6 @@ % endfor +
From 6ef8713c9406c850c2da21b120cdf822bdb77e4d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 10:55:05 -0500 Subject: [PATCH 027/171] JS state tracking --- .../xmodule/combined_open_ended_module.py | 27 ++++++++++++++++--- .../js/src/combinedopenended/display.coffee | 5 ++-- .../xmodule/xmodule/self_assessment_module.py | 2 +- lms/templates/combined_open_ended.html | 2 +- 4 files changed, 29 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 58d0cd4560..a57bf965d0 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -131,6 +131,7 @@ class CombinedOpenEndedModule(XModule): 'items': [{'content' : task_html}], 'ajax_url': self.system.ajax_url, 'allow_reset': True, + 'state' : self.state, } html = self.system.render_template('combined_open_ended.html', context) @@ -201,12 +202,32 @@ class CombinedOpenEndedModule(XModule): return self.update_task_states_ajax(return_html) def next_problem(self): - pass + self.setup_next_task() + return {'success' : True} def reset(self): - pass + """ + If resetting is allowed, reset the state. - def get_instance_state(self): + Returns {'success': bool, 'error': msg} + (error only present if not success) + """ + if self.state != self.DONE: + return self.out_of_sync_error(get) + + if self.attempts > self.max_attempts: + return { + 'success': False, + 'error': 'Too many attempts.' + } + self.state=self.INITIAL + self.current_task_number=0 + self.setup_next_task() + self.current_task.reset(self.system) + return {'success': True} + + +def get_instance_state(self): """ Get the current score and state """ diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 0e8cadd69f..eab904b0a4 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -59,6 +59,7 @@ class @CombinedOpenEnded @submit_button.hide() if !@state == 'done' @next_problem_button.show() + if @state == 'done' if @allow_reset @reset_button.show() else @@ -127,7 +128,7 @@ class @CombinedOpenEnded reset: (event) => event.preventDefault() @errors_area.html('Problem state got out of sync. Try reloading the page.') - if @child_state == 'done' + if @state == 'done' $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => if response.success @answer_area.val('') @@ -142,7 +143,7 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') - next_problem (event) => + next_problem: (event) => event.preventDefault() @errors_area.html('Problem state got out of sync. Try reloading the page.') if @child_state == 'done' diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index cba13c7e54..c1c568af2f 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -459,7 +459,7 @@ class SelfAssessmentModule(): 'allow_reset': self._allow_reset()} - def reset(self, get, system): + def reset(self, system): """ If resetting is allowed, reset the state. diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 51afddcd15..a00d5b042f 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,4 +1,4 @@ -
+
% for item in items:
${item['content'] | n}
From 70d0e6a1b601422f4c24e1420924c0a77e4b0455 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:11:35 -0500 Subject: [PATCH 028/171] Silly tab error --- .../xmodule/xmodule/combined_open_ended_module.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index a57bf965d0..a73886c7e2 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -201,11 +201,14 @@ class CombinedOpenEndedModule(XModule): return_html = self.current_task.handle_ajax(dispatch,get, self.system) return self.update_task_states_ajax(return_html) - def next_problem(self): + d = handlers[dispatch](get) + return json.dumps(d,cls=ComplexEncoder) + + def next_problem(self, get): self.setup_next_task() return {'success' : True} - def reset(self): + def reset(self, get): """ If resetting is allowed, reset the state. @@ -221,13 +224,15 @@ class CombinedOpenEndedModule(XModule): 'error': 'Too many attempts.' } self.state=self.INITIAL + for i in xrange(0,len(self.task_xml)): + self.current_task_number=i + self.setup_next_task() + self.current_task.reset(self.system) self.current_task_number=0 - self.setup_next_task() - self.current_task.reset(self.system) return {'success': True} -def get_instance_state(self): + def get_instance_state(self): """ Get the current score and state """ From dabc2e0c9d33b0346c348834d003a0fa339e31c8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:21:08 -0500 Subject: [PATCH 029/171] Javascript state transitions --- .../js/src/combinedopenended/display.coffee | 6 +++++- .../lib/xmodule/xmodule/self_assessment_module.py | 14 +++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index eab904b0a4..8e1dff4d99 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -57,8 +57,10 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() - if !@state == 'done' + if @state != 'done' @next_problem_button.show() + else + @next_problem_button.hide() if @state == 'done' if @allow_reset @reset_button.show() @@ -138,6 +140,7 @@ class @CombinedOpenEnded @child_state = 'initial' @rebind() @reset_button.hide() + location.reload() else @errors_area.html(response.error) else @@ -156,6 +159,7 @@ class @CombinedOpenEnded @child_state = 'initial' @rebind() @reset_button.hide() + location.reload() else @errors_area.html(response.error) else diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index c1c568af2f..64cf140d38 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -466,14 +466,14 @@ class SelfAssessmentModule(): Returns {'success': bool, 'error': msg} (error only present if not success) """ - if self.state != self.DONE: - return self.out_of_sync_error(get) + #if self.state != self.DONE: + # return self.out_of_sync_error(get) - if self.attempts > self.max_attempts: - return { - 'success': False, - 'error': 'Too many attempts.' - } + #if self.attempts > self.max_attempts: + # return { + # 'success': False, + # 'error': 'Too many attempts.' + # } self.change_state(self.INITIAL) return {'success': True} From d910395c7de4d07b5b1401bf05918ab036eda554 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:24:05 -0500 Subject: [PATCH 030/171] Task count tracking --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 ++ lms/templates/combined_open_ended.html | 2 +- 2 files 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 a73886c7e2..afa5f61b25 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -132,6 +132,8 @@ class CombinedOpenEndedModule(XModule): 'ajax_url': self.system.ajax_url, 'allow_reset': True, 'state' : self.state, + 'task_count' : len(self.task_xml), + 'task_number' : self.current_task_number, } html = self.system.render_template('combined_open_ended.html', context) diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index a00d5b042f..5b6823e809 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,4 +1,4 @@ -
+
% for item in items:
${item['content'] | n}
From 69d8c4a97719154801d79322b84cfe985255243a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:27:35 -0500 Subject: [PATCH 031/171] javascript state tracking for next problem and reset --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- .../xmodule/js/src/combinedopenended/display.coffee | 7 +++++-- 2 files changed, 6 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 afa5f61b25..f33439673c 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -133,7 +133,7 @@ class CombinedOpenEndedModule(XModule): 'allow_reset': True, 'state' : self.state, 'task_count' : len(self.task_xml), - 'task_number' : self.current_task_number, + 'task_number' : self.current_task_number+1, } html = self.system.render_template('combined_open_ended.html', context) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 8e1dff4d99..34b9b0c472 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -4,6 +4,9 @@ class @CombinedOpenEnded @id = @el.data('id') @ajax_url = @el.data('ajax-url') @state = @el.data('state') + @task_count = @el.data('task-count') + @task_number = @el.data('task-number') + @allow_reset = @el.data('allow_reset') @reset_button = @$('.reset-button') @reset_button.click @reset @@ -57,16 +60,16 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() - if @state != 'done' + if @task_number<@task_count @next_problem_button.show() else @next_problem_button.hide() - if @state == 'done' if @allow_reset @reset_button.show() else @reset_button.hide() + find_assessment_elements: -> @assessment = @$('select.assessment') From 844fb03377aa9f70789e261184f1282bcfcf8a3b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:35:27 -0500 Subject: [PATCH 032/171] javascript transitions okay, can integrate open ended response now --- .../js/src/combinedopenended/display.coffee | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 34b9b0c472..08c7aafa4d 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -64,10 +64,10 @@ class @CombinedOpenEnded @next_problem_button.show() else @next_problem_button.hide() - if @allow_reset - @reset_button.show() - else - @reset_button.hide() + #if @allow_reset + @reset_button.show() + #else + # @reset_button.hide() find_assessment_elements: -> @@ -132,8 +132,7 @@ class @CombinedOpenEnded reset: (event) => event.preventDefault() - @errors_area.html('Problem state got out of sync. Try reloading the page.') - if @state == 'done' + if @child_state == 'done' $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => if response.success @answer_area.val('') @@ -151,7 +150,6 @@ class @CombinedOpenEnded next_problem: (event) => event.preventDefault() - @errors_area.html('Problem state got out of sync. Try reloading the page.') if @child_state == 'done' $.postWithPrefix "#{@ajax_url}/next_problem", {}, (response) => if response.success @@ -161,7 +159,7 @@ class @CombinedOpenEnded @message_wrapper.html('') @child_state = 'initial' @rebind() - @reset_button.hide() + @next_problem_button.hide() location.reload() else @errors_area.html(response.error) From e3a311d28347a9bd128a26281b914265c93801e6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:40:49 -0500 Subject: [PATCH 033/171] Start creating open ended xmodule --- common/lib/xmodule/open_ended_module.py | 128 ++++++++++++++++++ .../xmodule/xmodule/self_assessment_module.py | 9 +- 2 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 common/lib/xmodule/open_ended_module.py diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py new file mode 100644 index 0000000000..e728a8dba3 --- /dev/null +++ b/common/lib/xmodule/open_ended_module.py @@ -0,0 +1,128 @@ +""" +A Self Assessment module that allows students to write open-ended responses, +submit, then see a rubric and rate themselves. Persists student supplied +hints, answers, and assessment judgment (currently only correct/incorrect). +Parses xml definition file--see below for exact format. +""" + +import copy +from fs.errors import ResourceNotFoundError +import itertools +import json +import logging +from lxml import etree +from lxml.html import rewrite_links +from path import path +import os +import sys + +from pkg_resources import resource_string + +from .capa_module import only_one, ComplexEncoder +from .editing_module import EditingDescriptor +from .html_checker import check_html +from progress import Progress +from .stringify import stringify_children +from .x_module import XModule +from .xml_module import XmlDescriptor +from xmodule.modulestore import Location + +log = logging.getLogger("mitx.courseware") + +# Set the default number of max attempts. Should be 1 for production +# Set higher for debugging/testing +# attempts specified in xml definition overrides this. +MAX_ATTEMPTS = 1 + +# Set maximum available number of points. +# Overriden by max_score specified in xml. +MAX_SCORE = 1 + +class OpenEndedModule(): + """ + States: + + initial (prompt, textbox shown) + | + assessing (read-only textbox, rubric + assessment input shown) + | + request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) + | + done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows + a reset button that goes back to initial state. Saves previous + submissions too.) + """ + + DEFAULT_QUEUE = 'open-ended' + DEFAULT_MESSAGE_QUEUE = 'open-ended-message' + response_tag = 'openendedresponse' + allowed_inputfields = ['openendedinput'] + max_inputfields = 1 + + STATE_VERSION = 1 + + # states + INITIAL = 'initial' + ASSESSING = 'assessing' + REQUEST_HINT = 'post_assessment' + DONE = 'done' + + def __init__(self, system, location, definition, descriptor, + instance_state=None, shared_state=None, **kwargs): + """ + Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, + and two optional attributes: + attempts, which should be an integer that defaults to 1. + If it's > 1, the student will be able to re-submit after they see + the rubric. + max_score, which should be an integer that defaults to 1. + It defines the maximum number of points a student can get. Assumed to be integer scale + from 0 to max_score, with an interval of 1. + + Note: all the submissions are stored. + + Sample file: + + + + Insert prompt text here. (arbitrary html) + + + Insert grading rubric here. (arbitrary html) + + + Please enter a hint below: (arbitrary html) + + + Thanks for submitting! (arbitrary html) + + + """ + + # Load instance state + if instance_state is not None: + instance_state = json.loads(instance_state) + else: + instance_state = {} + + instance_state = self.convert_state_to_current_format(instance_state) + + # History is a list of tuples of (answer, score, hint), where hint may be + # None for any element, and score and hint can be None for the last (current) + # element. + # Scores are on scale from 0 to max_score + self.history = instance_state.get('history', []) + + self.state = instance_state.get('state', 'initial') + + self.attempts = instance_state.get('attempts', 0) + self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) + + # Used for progress / grading. Currently get credit just for + # completion (doesn't matter if you self-assessed correct/incorrect). + self._max_score = int(instance_state.get('max_score', MAX_SCORE)) + + self.rubric = definition['rubric'] + self.prompt = definition['prompt'] + self.submit_message = definition['submitmessage'] + self.hint_prompt = definition['hintprompt'] \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 64cf140d38..0b7cc90994 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -93,15 +93,18 @@ class SelfAssessmentModule(): """ + self.xml = xml + self.inputfields = inputfields + self.context = context + self.system = system + # Load instance state if instance_state is not None: instance_state = json.loads(instance_state) else: instance_state = {} - instance_state = self.convert_state_to_current_format(instance_state) - - # History is a list of tuples of (answer, score, hint), where hint may be + # History is a list of tuples of (answer, score, feedback), where hint may be # 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 From 6d61bd469a096eef5eca94cb88dbc3a5f13e9e2e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:47:49 -0500 Subject: [PATCH 034/171] initial setup for open ended xmodule --- common/lib/xmodule/open_ended_module.py | 59 +++++++++++++++++-- .../xmodule/xmodule/self_assessment_module.py | 5 -- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index e728a8dba3..62310f1e18 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -64,7 +64,7 @@ class OpenEndedModule(): # states INITIAL = 'initial' ASSESSING = 'assessing' - REQUEST_HINT = 'post_assessment' + POST_ASSESSMENT = 'post_assessment' DONE = 'done' def __init__(self, system, location, definition, descriptor, @@ -122,7 +122,56 @@ class OpenEndedModule(): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(instance_state.get('max_score', MAX_SCORE)) - self.rubric = definition['rubric'] - self.prompt = definition['prompt'] - self.submit_message = definition['submitmessage'] - self.hint_prompt = definition['hintprompt'] \ No newline at end of file + oeparam = definition['openendedparam'] + prompt = definition['prompt'] + rubric = definition['rubric'] + + self.url = definition.get('url', None) + self.queue_name = definition.get('queuename', self.DEFAULT_QUEUE) + self.message_queue_name = definition.get('message-queuename', self.DEFAULT_MESSAGE_QUEUE) + + #This is needed to attach feedback to specific responses later + self.submission_id=None + self.grader_id=None + + if oeparam is None: + raise ValueError("No oeparam found in problem xml.") + if prompt is None: + raise ValueError("No prompt found in problem xml.") + if rubric is None: + raise ValueError("No rubric found in problem xml.") + + self._parse(oeparam, prompt, rubric) + + + def handle_ajax(self, dispatch, get): + ''' + This is called by courseware.module_render, to handle an AJAX call. + "get" is request.POST. + + Returns a json dictionary: + { 'progress_changed' : True/False, + 'progress' : 'none'/'in_progress'/'done', + } + ''' + handlers = { + 'problem_get': self.get_problem, + 'problem_check': self.check_problem, + 'problem_reset': self.reset_problem, + 'problem_save': self.save_problem, + 'problem_show': self.get_answer, + 'score_update': self.update_score, + 'message_post' : self.message_post, + } + + if dispatch not in handlers: + return 'Error' + + before = self.get_progress() + d = handlers[dispatch](get) + after = self.get_progress() + d.update({ + 'progress_changed': after != before, + 'progress_status': Progress.to_js_status_str(after), + }) + return json.dumps(d, cls=ComplexEncoder) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 0b7cc90994..1b10fab9ac 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -93,11 +93,6 @@ class SelfAssessmentModule(): """ - self.xml = xml - self.inputfields = inputfields - self.context = context - self.system = system - # Load instance state if instance_state is not None: instance_state = json.loads(instance_state) From 6ac90cf9544d89f3cf7da3f5a78066922af6a6fd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 11:53:33 -0500 Subject: [PATCH 035/171] Fix message post to work with ajax --- common/lib/xmodule/open_ended_module.py | 371 ++++++++++++++++++++++++ 1 file changed, 371 insertions(+) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index 62310f1e18..0ce2007c21 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -15,6 +15,8 @@ from lxml.html import rewrite_links from path import path import os import sys +import hashlib +import capa.xqueue_interface as xqueue_interface from pkg_resources import resource_string @@ -143,6 +145,375 @@ class OpenEndedModule(): self._parse(oeparam, prompt, rubric) + def _parse(self, oeparam, prompt, rubric): + ''' + Parse OpenEndedResponse XML: + self.initial_display + self.payload - dict containing keys -- + 'grader' : path to grader settings file, 'problem_id' : id of the problem + + self.answer - What to display when show answer is clicked + ''' + # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload + prompt_string = self.stringify_children(prompt) + rubric_string = self.stringify_children(rubric) + + grader_payload = oeparam.find('grader_payload') + grader_payload = grader_payload.text if grader_payload is not None else '' + + #Update grader payload with student id. If grader payload not json, error. + try: + parsed_grader_payload = json.loads(grader_payload) + # NOTE: self.system.location is valid because the capa_module + # __init__ adds it (easiest way to get problem location into + # response types) + except TypeError, ValueError: + log.exception("Grader payload %r is not a json object!", grader_payload) + + self.initial_display = find_with_default(oeparam, 'initial_display', '') + self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') + + parsed_grader_payload.update({ + 'location' : self.system.location, + 'course_id' : self.system.course_id, + 'prompt' : prompt_string, + 'rubric' : rubric_string, + 'initial_display' : self.initial_display, + 'answer' : self.answer, + }) + updated_grader_payload = json.dumps(parsed_grader_payload) + + self.payload = {'grader_payload': updated_grader_payload} + + try: + self.max_score = int(find_with_default(oeparam, 'max_score', 1)) + except ValueError: + self.max_score = 1 + + def handle_message_post(self,get): + """ + Handles a student message post (a reaction to the grade they received from an open ended grader type) + Returns a boolean success/fail and an error message + """ + + event_info = dict() + event_info['problem_id'] = self.location.url() + event_info['student_id'] = self.system.anonymous_student_id + event_info['survey_responses']= get + + survey_responses=event_info['survey_responses'] + for tag in ['feedback', 'submission_id', 'grader_id', 'score']: + if tag not in survey_responses: + return False, "Could not find needed tag {0}".format(tag) + try: + submission_id=int(survey_responses['submission_id']) + grader_id = int(survey_responses['grader_id']) + feedback = str(survey_responses['feedback'].encode('ascii', 'ignore')) + score = int(survey_responses['score']) + except: + error_message=("Could not parse submission id, grader id, " + "or feedback from message_post ajax call. Here is the message data: {0}".format(survey_responses)) + log.exception(error_message) + return False, "There was an error saving your feedback. Please contact course staff." + + qinterface = self.system.xqueue['interface'] + qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) + anonymous_student_id = self.system.anonymous_student_id + queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + anonymous_student_id + + self.answer_id) + + xheader = xqueue_interface.make_xheader( + lms_callback_url=self.system.xqueue['callback_url'], + lms_key=queuekey, + queue_name=self.message_queue_name + ) + + student_info = {'anonymous_student_id': anonymous_student_id, + 'submission_time': qtime, + } + contents= { + 'feedback' : feedback, + 'submission_id' : submission_id, + 'grader_id' : grader_id, + 'score': score, + 'student_info' : json.dumps(student_info), + } + + (error, msg) = qinterface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + #Convert error to a success value + success=True + if error: + success=False + + return success, "Successfully submitted your feedback." + + def get_score(self, student_answers): + + try: + submission = student_answers[self.answer_id] + except KeyError: + msg = ('Cannot get student answer for answer_id: {0}. student_answers {1}' + .format(self.answer_id, student_answers)) + log.exception(msg) + raise LoncapaProblemError(msg) + + # Prepare xqueue request + #------------------------------------------------------------ + + qinterface = self.system.xqueue['interface'] + qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) + + anonymous_student_id = self.system.anonymous_student_id + + # Generate header + queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + anonymous_student_id + + self.answer_id) + + xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue['callback_url'], + lms_key=queuekey, + queue_name=self.queue_name) + + self.context.update({'submission': submission}) + + contents = self.payload.copy() + + # Metadata related to the student submission revealed to the external grader + student_info = {'anonymous_student_id': anonymous_student_id, + 'submission_time': qtime, + } + + #Update contents with student response and student info + contents.update({ + 'student_info': json.dumps(student_info), + 'student_response': submission, + 'max_score' : self.max_score, + }) + + # Submit request. When successful, 'msg' is the prior length of the queue + (error, msg) = qinterface.send_to_queue(header=xheader, + body=json.dumps(contents)) + + # State associated with the queueing request + queuestate = {'key': queuekey, + 'time': qtime,} + + cmap = CorrectMap() + if error: + cmap.set(self.answer_id, queuestate=None, + msg='Unable to deliver your submission to grader. (Reason: {0}.)' + ' Please try again later.'.format(msg)) + else: + # Queueing mechanism flags: + # 1) Backend: Non-null CorrectMap['queuestate'] indicates that + # the problem has been queued + # 2) Frontend: correctness='incomplete' eventually trickles down + # through inputtypes.textbox and .filesubmission to inform the + # browser that the submission is queued (and it could e.g. poll) + cmap.set(self.answer_id, queuestate=queuestate, + correctness='incomplete', msg=msg) + + return cmap + + def update_score(self, score_msg, oldcmap, queuekey): + log.debug(score_msg) + score_msg = self._parse_score_msg(score_msg) + if not score_msg.valid: + oldcmap.set(self.answer_id, + msg = 'Invalid grader reply. Please contact the course staff.') + return oldcmap + + correctness = 'correct' if score_msg.correct else 'incorrect' + + # TODO: Find out how this is used elsewhere, if any + self.context['correct'] = correctness + + # Replace 'oldcmap' with new grading results if queuekey matches. If queuekey + # does not match, we keep waiting for the score_msg whose key actually matches + if oldcmap.is_right_queuekey(self.answer_id, queuekey): + # Sanity check on returned points + points = score_msg.points + if points < 0: + points = 0 + + # Queuestate is consumed, so reset it to None + oldcmap.set(self.answer_id, npoints=points, correctness=correctness, + msg = score_msg.msg.replace(' ', ' '), queuestate=None) + else: + log.debug('OpenEndedResponse: queuekey {0} does not match for answer_id={1}.'.format( + queuekey, self.answer_id)) + + return oldcmap + + + def get_answers(self): + anshtml = '
{0}
'.format(self.answer) + return {self.answer_id: anshtml} + + def get_initial_display(self): + return {self.answer_id: self.initial_display} + + def _convert_longform_feedback_to_html(self, response_items): + """ + Take in a dictionary, and return html strings for display to student. + Input: + response_items: Dictionary with keys success, feedback. + if success is True, feedback should be a dictionary, with keys for + types of feedback, and the corresponding feedback values. + if success is False, feedback is actually an error string. + + NOTE: this will need to change when we integrate peer grading, because + that will have more complex feedback. + + Output: + String -- html that can be displayed to the student. + """ + + # We want to display available feedback in a particular order. + # This dictionary specifies which goes first--lower first. + priorities = {# These go at the start of the feedback + 'spelling': 0, + 'grammar': 1, + # needs to be after all the other feedback + 'markup_text': 3} + + default_priority = 2 + + def get_priority(elt): + """ + Args: + elt: a tuple of feedback-type, feedback + Returns: + the priority for this feedback type + """ + return priorities.get(elt[0], default_priority) + + def encode_values(feedback_type,value): + feedback_type=str(feedback_type).encode('ascii', 'ignore') + if not isinstance(value,basestring): + value=str(value) + value=value.encode('ascii', 'ignore') + return feedback_type,value + + def format_feedback(feedback_type, value): + feedback_type,value=encode_values(feedback_type,value) + feedback= """ +
+ {value} +
+ """.format(feedback_type=feedback_type, value=value) + return feedback + + def format_feedback_hidden(feedback_type , value): + feedback_type,value=encode_values(feedback_type,value) + feedback = """ + + """.format(feedback_type=feedback_type, value=value) + return feedback + + # TODO (vshnayder): design and document the details of this format so + # that we can do proper escaping here (e.g. are the graders allowed to + # include HTML?) + + for tag in ['success', 'feedback', 'submission_id', 'grader_id']: + if tag not in response_items: + return format_feedback('errors', 'Error getting feedback') + + feedback_items = response_items['feedback'] + try: + feedback = json.loads(feedback_items) + except (TypeError, ValueError): + log.exception("feedback_items have invalid json %r", feedback_items) + return format_feedback('errors', 'Could not parse feedback') + + if response_items['success']: + if len(feedback) == 0: + return format_feedback('errors', 'No feedback available') + + feedback_lst = sorted(feedback.items(), key=get_priority) + feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst) + else: + feedback_list_part1 = format_feedback('errors', response_items['feedback']) + + feedback_list_part2=(u"\n".join([format_feedback_hidden(feedback_type,value) + for feedback_type,value in response_items.items() + if feedback_type in ['submission_id', 'grader_id']])) + + return u"\n".join([feedback_list_part1,feedback_list_part2]) + + def _format_feedback(self, response_items): + """ + Input: + Dictionary called feedback. Must contain keys seen below. + Output: + Return error message or feedback template + """ + + feedback = self._convert_longform_feedback_to_html(response_items) + + if not response_items['success']: + return self.system.render_template("open_ended_error.html", + {'errors' : feedback}) + + feedback_template = self.system.render_template("open_ended_feedback.html", { + 'grader_type': response_items['grader_type'], + 'score': "{0} / {1}".format(response_items['score'], self.max_score), + 'feedback': feedback, + }) + + return feedback_template + + + def _parse_score_msg(self, score_msg): + """ + Grader reply is a JSON-dump of the following dict + { 'correct': True/False, + 'score': Numeric value (floating point is okay) to assign to answer + 'msg': grader_msg + 'feedback' : feedback from grader + } + + Returns (valid_score_msg, correct, score, msg): + valid_score_msg: Flag indicating valid score_msg format (Boolean) + correct: Correctness of submission (Boolean) + score: Points to be assigned (numeric, can be float) + """ + fail = ScoreMessage(valid=False, correct=False, points=0, msg='') + try: + score_result = json.loads(score_msg) + except (TypeError, ValueError): + log.error("External grader message should be a JSON-serialized dict." + " Received score_msg = {0}".format(score_msg)) + return fail + + if not isinstance(score_result, dict): + log.error("External grader message should be a JSON-serialized dict." + " Received score_result = {0}".format(score_result)) + return fail + + for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']: + if tag not in score_result: + log.error("External grader message is missing required tag: {0}" + .format(tag)) + return fail + + feedback = self._format_feedback(score_result) + self.submission_id=score_result['submission_id'] + self.grader_id=score_result['grader_id'] + + # HACK: for now, just assume it's correct if you got more than 2/3. + # Also assumes that score_result['score'] is an integer. + score_ratio = int(score_result['score']) / float(self.max_score) + correct = (score_ratio >= 0.66) + + #Currently ignore msg and only return feedback (which takes the place of msg) + return ScoreMessage(valid=True, correct=correct, + points=score_result['score'], msg=feedback) def handle_ajax(self, dispatch, get): ''' From 54c763d8c46db40bdb1a416559b46821385e0c9d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:19:39 -0500 Subject: [PATCH 036/171] set up template for open ended xmodule --- common/lib/capa/capa/inputtypes.py | 2 +- common/lib/xmodule/open_ended_module.py | 62 +++++++++++++++++++++++-- lms/templates/open_ended.html | 56 ++++++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 lms/templates/open_ended.html diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index e3eb47acc5..26c97ed907 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -742,7 +742,7 @@ class OpenEndedInput(InputTypeBase): etc. """ - template = "openendedinput.html" + template = tags = ['openendedinput'] # pulled out for testing diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index 0ce2007c21..d087b5310c 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -200,7 +200,7 @@ class OpenEndedModule(): event_info['problem_id'] = self.location.url() event_info['student_id'] = self.system.anonymous_student_id event_info['survey_responses']= get - + survey_responses=event_info['survey_responses'] for tag in ['feedback', 'submission_id', 'grader_id', 'score']: if tag not in survey_responses: @@ -318,7 +318,7 @@ class OpenEndedModule(): return cmap - def update_score(self, score_msg, oldcmap, queuekey): + def _update_score(self, score_msg, oldcmap, queuekey): log.debug(score_msg) score_msg = self._parse_score_msg(score_msg) if not score_msg.valid: @@ -530,7 +530,6 @@ class OpenEndedModule(): 'problem_check': self.check_problem, 'problem_reset': self.reset_problem, 'problem_save': self.save_problem, - 'problem_show': self.get_answer, 'score_update': self.update_score, 'message_post' : self.message_post, } @@ -546,3 +545,60 @@ class OpenEndedModule(): 'progress_status': Progress.to_js_status_str(after), }) return json.dumps(d, cls=ComplexEncoder) + + def get_problem: + return {'html': self.get_problem_html(encapsulate=False)} + + def check_problem: + pass + + def reset_problem: + pass + + def save_problem: + pass + + def update_score: + """ + Delivers grading response (e.g. from asynchronous code checking) to + the capa problem, so its score can be updated + + 'get' must have a field 'response' which is a string that contains the + grader's response + + No ajax return is needed. Return empty dict. + """ + queuekey = get['queuekey'] + score_msg = get['xqueue_body'] + #TODO: Remove need for cmap + self._update_score(score_msg, queuekey) + + return dict() # No AJAX return is needed + + def get_html(self): + """ + Implement special logic: handle queueing state, and default input. + """ + # if no student input yet, then use the default input given by the problem + if not self.value: + self.value = self.xml.text + + # Check if problem has been queued + self.queue_len = 0 + # Flag indicating that the problem has been queued, 'msg' is length of queue + if self.status == 'incomplete': + self.status = 'queued' + self.queue_len = self.msg + self.msg = self.submitted_msg + + context={'rows' : 30, + 'cols' : 80, + 'hidden' : '', + } + + html=self.system.render_template("openendedinput.html", context) + + def _extra_context(self): + """Defined queue_len, add it """ + return {'queue_len': self.queue_len,} + diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html new file mode 100644 index 0000000000..c42ad73faf --- /dev/null +++ b/lms/templates/open_ended.html @@ -0,0 +1,56 @@ +
+ + +
+ % if status == 'unsubmitted': + Unanswered + % elif status == 'correct': + Correct + % elif status == 'incorrect': + Incorrect + % elif status == 'queued': + Submitted for grading + % endif + + % if hidden: +
+ % endif +
+ + + + % if status == 'queued': + + % endif +
+ ${msg|n} + % if status in ['correct','incorrect']: +
+
+ Respond to Feedback +
+
+

How accurate do you find this feedback?

+
+
    +
  • +
  • +
  • +
  • +
  • +
+
+

Additional comments:

+ +
+ +
+
+
+ % endif +
+
From 56f5434bfdb2100e94e8c82424ccb92fe0552d1f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:22:41 -0500 Subject: [PATCH 037/171] Initial html for open ended xmodule --- common/lib/xmodule/open_ended_module.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index d087b5310c..c86b18e639 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -594,9 +594,14 @@ class OpenEndedModule(): context={'rows' : 30, 'cols' : 80, 'hidden' : '', + 'id' : 'open_ended', + 'msg' : self.msg, + 'status' : self.status, + 'queue_len' : self.queue_len, + 'value' : self.value, } - html=self.system.render_template("openendedinput.html", context) + html=self.system.render_template("open_ended.html", context) def _extra_context(self): """Defined queue_len, add it """ From 1b9deb881bafa3dd7be36a7b94e1771aea8cdbcf Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:29:24 -0500 Subject: [PATCH 038/171] Open ended descriptor --- common/lib/xmodule/open_ended_module.py | 100 +++++++++++++++++++++--- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index c86b18e639..6380dc16d8 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -527,7 +527,6 @@ class OpenEndedModule(): ''' handlers = { 'problem_get': self.get_problem, - 'problem_check': self.check_problem, 'problem_reset': self.reset_problem, 'problem_save': self.save_problem, 'score_update': self.update_score, @@ -547,13 +546,12 @@ class OpenEndedModule(): return json.dumps(d, cls=ComplexEncoder) def get_problem: - return {'html': self.get_problem_html(encapsulate=False)} - - def check_problem: - pass + return self.get_html() def reset_problem: - pass + self.change_state(self.INITIAL) + return {'success': True} + def save_problem: pass @@ -602,8 +600,92 @@ class OpenEndedModule(): } html=self.system.render_template("open_ended.html", context) + return html + + 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: + return + + self.state = new_state + + if self.state == self.DONE: + self.attempts += 1 + + def get_instance_state(self): + """ + Get the current score and state + """ + + state = { + 'version': self.STATE_VERSION, + 'history': self.history, + 'state': self.state, + 'max_score': self._max_score, + 'attempts': self.attempts, + } + return json.dumps(state) + + +class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): + """ + Module for adding self assessment questions to courses + """ + mako_template = "widgets/html-edit.html" + module_class = OpenEndedModule + filename_extension = "xml" + + stores_state = True + has_score = True + template_dir_name = openended" + + js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} + js_module_name = "HTMLEditingDescriptor" + expected_children = ['rubric', 'prompt', 'oeparam'] + + @classmethod + def definition_from_xml(cls, xml_object, system): + """ + Pull out the rubric, prompt, and submitmessage into a dictionary. + + Returns: + { + 'rubric': 'some-html', + 'prompt': 'some-html', + 'submitmessage': 'some-html' + 'hintprompt': 'some-html' + } + """ + + for child in self.expected_children: + if len(xml_object.xpath(child)) != 1: + raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child)) + + def parse(k): + """Assumes that xml_object has child k""" + return stringify_children(xml_object.xpath(k)[0]) + + return {'rubric': parse('rubric'), + 'prompt': parse('prompt'), + 'oeparam': parse('oeparam'), + } + + + def definition_to_xml(self, resource_fs): + '''Return an xml element representing this definition.''' + elt = etree.Element('openended') + + def add_child(k): + child_str = '<{tag}>{body}'.format(tag=k, body=self.definition[k]) + child_node = etree.fromstring(child_str) + elt.append(child_node) + + for child in self.expected_children: + add_child(child) + + return elt - def _extra_context(self): - """Defined queue_len, add it """ - return {'queue_len': self.queue_len,} From 9d3f8ed0e52a09ecdc02bdea4edac0679fac9a68 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:37:10 -0500 Subject: [PATCH 039/171] Support open ended in combined open ended module --- common/lib/capa/capa/inputtypes.py | 2 +- .../xmodule/combined_open_ended_module.py | 27 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 26c97ed907..e3eb47acc5 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -742,7 +742,7 @@ class OpenEndedInput(InputTypeBase): etc. """ - template = + template = "openendedinput.html" tags = ['openendedinput'] # pulled out for testing diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index f33439673c..e544ebe3ae 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -20,6 +20,7 @@ from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location import self_assessment_module +import open_ended_module log = logging.getLogger("mitx.courseware") @@ -120,6 +121,24 @@ class CombinedOpenEndedModule(XModule): self.state=self.ASSESSING else: self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + elif current_task_type=="openended": + self.current_task_descriptor=open_ended_module.OpenEndedDescriptor(self.system) + self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) + if current_task_state is None and self.current_task_number==0: + self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING + elif current_task_state is None and self.current_task_number>0: + last_response, last_score=self.get_last_response(self.current_task_number-1) + current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + '"attempts": 0, "history": [{"answer": "' + str(last_response) + '"}]}') + {"state": "done", "version": 1, "max_score": 1, "attempts": 1, "history": [{"answer": "gdgddg", "score": 0, "hint": "dfdfdf"}]} + self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING + else: + self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + return True @@ -158,6 +177,12 @@ class CombinedOpenEndedModule(XModule): task=self_assessment_module.SelfAssessmentModule(self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) last_response=task.latest_answer() last_score = task.latest_score() + elif task_type=="openended": + task_descriptor=open_ended_module.OpenEndedDescriptor(self.system) + task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) + task=open_ended_module.OpenEndedModule(self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) + last_response=task.latest_answer() + last_score = task.latest_score() return last_response, last_score @@ -291,7 +316,7 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): def definition_to_xml(self, resource_fs): '''Return an xml element representing this definition.''' - elt = etree.Element('selfassessment') + elt = etree.Element('combinedopenended') def add_child(k): child_str = '<{tag}>{body}'.format(tag=k, body=self.definition[k]) From dabb5ee758f32ffa45164fe252a7733a397fd37b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:38:56 -0500 Subject: [PATCH 040/171] fix tab issue --- common/lib/xmodule/open_ended_module.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/open_ended_module.py index 6380dc16d8..163c518444 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/open_ended_module.py @@ -250,8 +250,7 @@ class OpenEndedModule(): return success, "Successfully submitted your feedback." - def get_score(self, student_answers): - + def get_score(self, student_answers): try: submission = student_answers[self.answer_id] except KeyError: From 0e81f916e1a06710ec70502419b392a38fed034a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:42:25 -0500 Subject: [PATCH 041/171] Move open ended module file --- .../xmodule/combined_open_ended_module.py | 4 ++-- .../{ => xmodule}/open_ended_module.py | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) rename common/lib/xmodule/{ => xmodule}/open_ended_module.py (98%) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index e544ebe3ae..9218de1baf 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -12,6 +12,7 @@ import sys from pkg_resources import resource_string from .capa_module import only_one, ComplexEncoder +from common.lib.xmodule.xmodule import open_ended_module from .editing_module import EditingDescriptor from .html_checker import check_html from progress import Progress @@ -20,7 +21,6 @@ from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location import self_assessment_module -import open_ended_module log = logging.getLogger("mitx.courseware") @@ -122,7 +122,7 @@ class CombinedOpenEndedModule(XModule): else: self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) elif current_task_type=="openended": - self.current_task_descriptor=open_ended_module.OpenEndedDescriptor(self.system) + self.current_task_descriptor= open_ended_module.OpenEndedDescriptor(self.system) self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) if current_task_state is None and self.current_task_number==0: self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) diff --git a/common/lib/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py similarity index 98% rename from common/lib/xmodule/open_ended_module.py rename to common/lib/xmodule/xmodule/open_ended_module.py index 163c518444..f5d6cbf8b5 100644 --- a/common/lib/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -544,18 +544,17 @@ class OpenEndedModule(): }) return json.dumps(d, cls=ComplexEncoder) - def get_problem: + def get_problem(self, get): return self.get_html() - def reset_problem: + def reset_problem(self, get): self.change_state(self.INITIAL) return {'success': True} - - def save_problem: + def save_problem(self, get): pass - def update_score: + def update_score(self, get): """ Delivers grading response (e.g. from asynchronous code checking) to the capa problem, so its score can be updated @@ -602,10 +601,10 @@ class OpenEndedModule(): return html 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. - """ + """ + 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: return @@ -639,7 +638,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): stores_state = True has_score = True - template_dir_name = openended" + template_dir_name = "openended" js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} js_module_name = "HTMLEditingDescriptor" From 480978cf3439f3a54ccbdea1f85c38456d14e3ba Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:45:33 -0500 Subject: [PATCH 042/171] Fix invalid import statement --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- common/lib/xmodule/xmodule/open_ended_module.py | 2 -- 2 files changed, 1 insertion(+), 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 9218de1baf..6692dd4947 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -12,7 +12,6 @@ import sys from pkg_resources import resource_string from .capa_module import only_one, ComplexEncoder -from common.lib.xmodule.xmodule import open_ended_module from .editing_module import EditingDescriptor from .html_checker import check_html from progress import Progress @@ -21,6 +20,7 @@ from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location import self_assessment_module +import open_ended_module log = logging.getLogger("mitx.courseware") diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index f5d6cbf8b5..5e8a4b3bec 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -57,8 +57,6 @@ class OpenEndedModule(): DEFAULT_QUEUE = 'open-ended' DEFAULT_MESSAGE_QUEUE = 'open-ended-message' - response_tag = 'openendedresponse' - allowed_inputfields = ['openendedinput'] max_inputfields = 1 STATE_VERSION = 1 From e9d24ad742be1f5ca71c50565a4cb11a41afa27c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:48:51 -0500 Subject: [PATCH 043/171] Fix descriptor --- common/lib/xmodule/xmodule/open_ended_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 5e8a4b3bec..2ccb89f2cb 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -640,7 +640,6 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]} js_module_name = "HTMLEditingDescriptor" - expected_children = ['rubric', 'prompt', 'oeparam'] @classmethod def definition_from_xml(cls, xml_object, system): @@ -656,7 +655,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): } """ - for child in self.expected_children: + for child in ['rubric', 'prompt', 'oeparam']: if len(xml_object.xpath(child)) != 1: raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child)) @@ -679,7 +678,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): child_node = etree.fromstring(child_str) elt.append(child_node) - for child in self.expected_children: + for child in ['rubric', 'prompt', 'oeparam']: add_child(child) return elt From 7351317c8ed732327360ec1a58daceeca789c4e8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:51:24 -0500 Subject: [PATCH 044/171] Fix tag naming --- common/lib/xmodule/xmodule/open_ended_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 2ccb89f2cb..d6de77d5f4 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -655,7 +655,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): } """ - for child in ['rubric', 'prompt', 'oeparam']: + for child in ['openendedrubric', 'prompt', 'openendedparam']: if len(xml_object.xpath(child)) != 1: raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child)) @@ -663,9 +663,9 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """Assumes that xml_object has child k""" return stringify_children(xml_object.xpath(k)[0]) - return {'rubric': parse('rubric'), + return {'rubric': parse('openendedrubric'), 'prompt': parse('prompt'), - 'oeparam': parse('oeparam'), + 'oeparam': parse('openendedparam'), } @@ -678,7 +678,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): child_node = etree.fromstring(child_str) elt.append(child_node) - for child in ['rubric', 'prompt', 'oeparam']: + for child in ['openendedrubric', 'prompt', 'openendedparam']: add_child(child) return elt From 6e7dae4f670a51c96018a0b41d8e2b25e0b98f4f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 12:53:12 -0500 Subject: [PATCH 045/171] Naming bugfixes --- common/lib/xmodule/xmodule/open_ended_module.py | 4 +--- common/lib/xmodule/xmodule/self_assessment_module.py | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index d6de77d5f4..4768f53656 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -105,8 +105,6 @@ class OpenEndedModule(): else: instance_state = {} - instance_state = self.convert_state_to_current_format(instance_state) - # History is a list of tuples of (answer, score, hint), where hint may be # None for any element, and score and hint can be None for the last (current) # element. @@ -122,7 +120,7 @@ class OpenEndedModule(): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(instance_state.get('max_score', MAX_SCORE)) - oeparam = definition['openendedparam'] + oeparam = definition['oeparam'] prompt = definition['prompt'] rubric = definition['rubric'] diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 1b10fab9ac..64cf140d38 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -99,7 +99,9 @@ class SelfAssessmentModule(): else: instance_state = {} - # History is a list of tuples of (answer, score, feedback), where hint may be + instance_state = self.convert_state_to_current_format(instance_state) + + # History is a list of tuples of (answer, score, hint), where hint may be # 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 From 2891ea4bff3378ea02b01efbf33caad4637be5cc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 13:03:09 -0500 Subject: [PATCH 046/171] Convert away from self.system to just using system --- .../lib/xmodule/xmodule/open_ended_module.py | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 4768f53656..3da03520ab 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -28,6 +28,7 @@ from .stringify import stringify_children from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location +from capa.util import * log = logging.getLogger("mitx.courseware") @@ -139,9 +140,9 @@ class OpenEndedModule(): if rubric is None: raise ValueError("No rubric found in problem xml.") - self._parse(oeparam, prompt, rubric) + self._parse(oeparam, prompt, rubric, system) - def _parse(self, oeparam, prompt, rubric): + def _parse(self, oeparam, prompt, rubric, system): ''' Parse OpenEndedResponse XML: self.initial_display @@ -151,8 +152,8 @@ class OpenEndedModule(): self.answer - What to display when show answer is clicked ''' # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload - prompt_string = self.stringify_children(prompt) - rubric_string = self.stringify_children(rubric) + prompt_string = stringify_children(prompt) + rubric_string = stringify_children(rubric) grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -170,8 +171,8 @@ class OpenEndedModule(): self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') parsed_grader_payload.update({ - 'location' : self.system.location, - 'course_id' : self.system.course_id, + 'location' : system.location, + 'course_id' : system.course_id, 'prompt' : prompt_string, 'rubric' : rubric_string, 'initial_display' : self.initial_display, @@ -186,15 +187,15 @@ class OpenEndedModule(): except ValueError: self.max_score = 1 - def handle_message_post(self,get): + def handle_message_post(self,get, system): """ Handles a student message post (a reaction to the grade they received from an open ended grader type) Returns a boolean success/fail and an error message """ event_info = dict() - event_info['problem_id'] = self.location.url() - event_info['student_id'] = self.system.anonymous_student_id + event_info['problem_id'] = system.location.url() + event_info['student_id'] = system.anonymous_student_id event_info['survey_responses']= get survey_responses=event_info['survey_responses'] @@ -212,15 +213,15 @@ class OpenEndedModule(): log.exception(error_message) return False, "There was an error saving your feedback. Please contact course staff." - qinterface = self.system.xqueue['interface'] + qinterface = system.xqueue['interface'] qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) - anonymous_student_id = self.system.anonymous_student_id - queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + anonymous_student_id = system.anonymous_student_id + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + self.answer_id) xheader = xqueue_interface.make_xheader( - lms_callback_url=self.system.xqueue['callback_url'], + lms_callback_url=system.xqueue['callback_url'], lms_key=queuekey, queue_name=self.message_queue_name ) @@ -246,7 +247,7 @@ class OpenEndedModule(): return success, "Successfully submitted your feedback." - def get_score(self, student_answers): + def get_score(self, student_answers, system): try: submission = student_answers[self.answer_id] except KeyError: @@ -258,17 +259,17 @@ class OpenEndedModule(): # Prepare xqueue request #------------------------------------------------------------ - qinterface = self.system.xqueue['interface'] + qinterface = system.xqueue['interface'] qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) - anonymous_student_id = self.system.anonymous_student_id + anonymous_student_id = system.anonymous_student_id # Generate header - queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + + queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + self.answer_id) - xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue['callback_url'], + xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'], lms_key=queuekey, queue_name=self.queue_name) @@ -510,7 +511,7 @@ class OpenEndedModule(): return ScoreMessage(valid=True, correct=correct, points=score_result['score'], msg=feedback) - def handle_ajax(self, dispatch, get): + def handle_ajax(self, dispatch, get, system): ''' This is called by courseware.module_render, to handle an AJAX call. "get" is request.POST. @@ -532,7 +533,7 @@ class OpenEndedModule(): return 'Error' before = self.get_progress() - d = handlers[dispatch](get) + d = handlers[dispatch](get, system) after = self.get_progress() d.update({ 'progress_changed': after != before, @@ -540,17 +541,17 @@ class OpenEndedModule(): }) return json.dumps(d, cls=ComplexEncoder) - def get_problem(self, get): - return self.get_html() + def get_problem(self, get, system): + return self.get_html(system) - def reset_problem(self, get): + def reset_problem(self, get, system): self.change_state(self.INITIAL) return {'success': True} - def save_problem(self, get): + def save_problem(self, get, system): pass - def update_score(self, get): + def update_score(self, get, system): """ Delivers grading response (e.g. from asynchronous code checking) to the capa problem, so its score can be updated @@ -563,11 +564,11 @@ class OpenEndedModule(): queuekey = get['queuekey'] score_msg = get['xqueue_body'] #TODO: Remove need for cmap - self._update_score(score_msg, queuekey) + self._update_score(score_msg, queuekey, system) return dict() # No AJAX return is needed - def get_html(self): + def get_html(self, system): """ Implement special logic: handle queueing state, and default input. """ @@ -593,7 +594,7 @@ class OpenEndedModule(): 'value' : self.value, } - html=self.system.render_template("open_ended.html", context) + html=system.render_template("open_ended.html", context) return html def change_state(self, new_state): @@ -659,7 +660,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): def parse(k): """Assumes that xml_object has child k""" - return stringify_children(xml_object.xpath(k)[0]) + return xml_object.xpath(k)[0] return {'rubric': parse('openendedrubric'), 'prompt': parse('prompt'), From a5eec6d60b609ba059b1b871a541d2cb034d1da2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 13:07:12 -0500 Subject: [PATCH 047/171] set location in system --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 6692dd4947..f5014af948 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -75,6 +75,7 @@ class CombinedOpenEndedModule(XModule): # 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 + system.set('location', location) self.current_task_number = instance_state.get('current_task_number', 0) self.task_states= instance_state.get('task_states', []) From ef27788cf5cdf784f746a1dbf787f2872bf845cd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 13:14:58 -0500 Subject: [PATCH 048/171] Initial display for open ended problem --- .../lib/xmodule/xmodule/open_ended_module.py | 50 +++++++++++++++---- lms/templates/open_ended.html | 10 ++-- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 3da03520ab..8edf7e0828 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -573,25 +573,26 @@ class OpenEndedModule(): Implement special logic: handle queueing state, and default input. """ # if no student input yet, then use the default input given by the problem - if not self.value: - self.value = self.xml.text + latest_answer=self.latest_answer() + if latest_answer is None: + value = self.initial_display # Check if problem has been queued self.queue_len = 0 # Flag indicating that the problem has been queued, 'msg' is length of queue - if self.status == 'incomplete': - self.status = 'queued' - self.queue_len = self.msg - self.msg = self.submitted_msg + if self.state == self.ASSESSING: + #self.queue_len = self.msg + #self.msg = self.submitted_msg + pass context={'rows' : 30, 'cols' : 80, 'hidden' : '', 'id' : 'open_ended', - 'msg' : self.msg, - 'status' : self.status, + 'msg' : "This is a message", + 'state' : self.state, 'queue_len' : self.queue_len, - 'value' : self.value, + 'value' : value, } html=system.render_template("open_ended.html", context) @@ -624,6 +625,37 @@ class OpenEndedModule(): } return json.dumps(state) + def latest_answer(self): + """None if not available""" + if not self.history: + return None + return self.history[-1].get('answer') + + def latest_score(self): + """None if not available""" + if not self.history: + return None + return self.history[-1].get('score') + + def latest_hint(self): + """None if not available""" + if not self.history: + return None + return self.history[-1].get('hint') + + def new_history_entry(self, answer): + self.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 + + def record_latest_hint(self, hint): + """Assumes that state is right, so we're adding a score to the latest + history element""" + self.history[-1]['hint'] = hint + class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index c42ad73faf..a6ffc684da 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -1,4 +1,4 @@ -
+
- % if status == 'unsubmitted': + % if state == 'initial': Unanswered - % elif status == 'correct': + % elif state == 'done': Correct - % elif status == 'incorrect': + % elif state == 'incorrect': Incorrect - % elif status == 'queued': + % elif state == 'assessing': Submitted for grading % endif From 6a5352620cf1aad9645384fa780fd5e96a8535a6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 13:20:51 -0500 Subject: [PATCH 049/171] Fix display errors --- .../lib/xmodule/xmodule/open_ended_module.py | 83 ++++++++++++------- lms/templates/open_ended.html | 2 +- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 8edf7e0828..bce571ebfc 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -154,6 +154,7 @@ class OpenEndedModule(): # 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 grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -568,36 +569,6 @@ class OpenEndedModule(): return dict() # No AJAX return is needed - def get_html(self, system): - """ - Implement special logic: handle queueing state, and default input. - """ - # if no student input yet, then use the default input given by the problem - latest_answer=self.latest_answer() - if latest_answer is None: - value = self.initial_display - - # Check if problem has been queued - self.queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue - if self.state == self.ASSESSING: - #self.queue_len = self.msg - #self.msg = self.submitted_msg - pass - - context={'rows' : 30, - 'cols' : 80, - 'hidden' : '', - 'id' : 'open_ended', - 'msg' : "This is a message", - 'state' : self.state, - 'queue_len' : self.queue_len, - 'value' : value, - } - - html=system.render_template("open_ended.html", context) - return html - def change_state(self, new_state): """ A centralized place for state changes--allows for hooks. If the @@ -656,6 +627,58 @@ class OpenEndedModule(): history element""" self.history[-1]['hint'] = hint + def _allow_reset(self): + """Can the module be reset?""" + return self.state == self.DONE and self.attempts < self.max_attempts + + def get_html(self, system): + #set context variables and render template + if self.state != self.INITIAL: + latest = self.latest_answer() + previous_answer = latest if latest is not None else '' + else: + previous_answer = '' + + context = { + 'prompt': self.prompt, + 'previous_answer': previous_answer, + 'state': self.state, + 'allow_reset': self._allow_reset(), + 'rows' : 30, + 'cols' : 80, + 'hidden' : '', + 'id' : 'open_ended', + } + + html = system.render_template('open_ended.html', context) + return html + + def max_score(self): + """ + Return max_score + """ + return self._max_score + + def get_score(self): + """ + Returns the last score in the list + """ + score = self.latest_score() + return {'score': score if score is not None else 0, + 'total': self._max_score} + + def get_progress(self): + ''' + For now, just return last score / max_score + ''' + if self._max_score > 0: + try: + return Progress(self.get_score()['score'], self._max_score) + except Exception as err: + log.exception("Got bad progress") + return None + return None + class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index a6ffc684da..a3e148d36b 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -1,4 +1,4 @@ -
+
+ >${previous_answer|h}
% if state == 'initial': @@ -21,14 +22,16 @@ % endif
+ + - % if status == 'queued': + % if state == 'assessing': % endif
${msg|n} - % if status in ['correct','incorrect']: + % if state == 'done':
Respond to Feedback From a4ad7800ed7b195019e5d05d9acfb3275b266b40 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 13:51:15 -0500 Subject: [PATCH 051/171] Add proper queue handling --- .../lib/xmodule/xmodule/open_ended_module.py | 48 ++++++++----------- lms/templates/open_ended.html | 2 +- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index b8f3bf8278..9b1b3463c1 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -188,7 +188,7 @@ class OpenEndedModule(): except ValueError: self.max_score = 1 - def handle_message_post(self,get, system): + def message_post(self,get, system): """ Handles a student message post (a reaction to the grade they received from an open ended grader type) Returns a boolean success/fail and an error message @@ -248,14 +248,7 @@ class OpenEndedModule(): return success, "Successfully submitted your feedback." - def get_score(self, student_answers, system): - try: - submission = student_answers[self.answer_id] - except KeyError: - msg = ('Cannot get student answer for answer_id: {0}. student_answers {1}' - .format(self.answer_id, student_answers)) - log.exception(msg) - raise LoncapaProblemError(msg) + def get_score(self, submission, system): # Prepare xqueue request #------------------------------------------------------------ @@ -297,23 +290,7 @@ class OpenEndedModule(): # State associated with the queueing request queuestate = {'key': queuekey, 'time': qtime,} - - cmap = CorrectMap() - if error: - cmap.set(self.answer_id, queuestate=None, - msg='Unable to deliver your submission to grader. (Reason: {0}.)' - ' Please try again later.'.format(msg)) - else: - # Queueing mechanism flags: - # 1) Backend: Non-null CorrectMap['queuestate'] indicates that - # the problem has been queued - # 2) Frontend: correctness='incomplete' eventually trickles down - # through inputtypes.textbox and .filesubmission to inform the - # browser that the submission is queued (and it could e.g. poll) - cmap.set(self.answer_id, queuestate=queuestate, - correctness='incomplete', msg=msg) - - return cmap + return True def _update_score(self, score_msg, oldcmap, queuekey): log.debug(score_msg) @@ -550,7 +527,24 @@ class OpenEndedModule(): return {'success': True} def save_problem(self, get, system): - pass + if self.attempts > self.max_attempts: + # If too many attempts, prevent student from saving answer and + # seeing rubric. In normal use, students shouldn't see this because + # they won't see the reset button once they're out of attempts. + return { + 'success': False, + 'error': 'Too many attempts.' + } + + if self.state != self.INITIAL: + return self.out_of_sync_error(get) + + # add new history element with answer and empty score and hint. + self.new_history_entry(get['student_answer']) + self.get_score(get['student_answer'], system) + self.change_state(self.ASSESSING) + + return {'success': True,} def update_score(self, get, system): """ diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 3456b61068..876d9a5ca1 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -23,7 +23,7 @@
- + % if state == 'assessing': From fc02e7e2692c4aa37ebeaf38003e31ee0f83dd1c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:00:15 -0500 Subject: [PATCH 052/171] Working on bugfixing --- common/lib/xmodule/xmodule/open_ended_module.py | 17 ++++++----------- lms/templates/open_ended.html | 8 ++++++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 9b1b3463c1..3dc4a787d5 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -30,6 +30,8 @@ from .xml_module import XmlDescriptor from xmodule.modulestore import Location from capa.util import * +from datetime import datetime + log = logging.getLogger("mitx.courseware") # Set the default number of max attempts. Should be 1 for production @@ -261,7 +263,7 @@ class OpenEndedModule(): # Generate header queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + - self.answer_id) + 1) xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'], lms_key=queuekey, @@ -499,10 +501,11 @@ class OpenEndedModule(): 'progress' : 'none'/'in_progress'/'done', } ''' + log.debug(get) handlers = { 'problem_get': self.get_problem, 'problem_reset': self.reset_problem, - 'problem_save': self.save_problem, + 'save_answer': self.save_answer, 'score_update': self.update_score, 'message_post' : self.message_post, } @@ -526,7 +529,7 @@ class OpenEndedModule(): self.change_state(self.INITIAL) return {'success': True} - def save_problem(self, get, system): + def save_answer(self, get, system): if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and # seeing rubric. In normal use, students shouldn't see this because @@ -654,14 +657,6 @@ class OpenEndedModule(): """ return self._max_score - def get_score(self): - """ - Returns the last score in the list - """ - score = self.latest_score() - return {'score': score if score is not None else 0, - 'total': self._max_score} - def get_progress(self): ''' For now, just return last score / max_score diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 876d9a5ca1..d682de5291 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -1,12 +1,16 @@
+ +
+
${prompt|n} - -
+
% if state == 'initial': Unanswered % elif state == 'done': From 825821ae885b178e1a70a481385356358e786d8f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:05:34 -0500 Subject: [PATCH 053/171] Fix key generation --- common/lib/xmodule/xmodule/open_ended_module.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 3dc4a787d5..fbd0662a63 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -263,14 +263,12 @@ class OpenEndedModule(): # Generate header queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + - 1) + str(len(self.history))) xheader = xqueue_interface.make_xheader(lms_callback_url=system.xqueue['callback_url'], lms_key=queuekey, queue_name=self.queue_name) - self.context.update({'submission': submission}) - contents = self.payload.copy() # Metadata related to the student submission revealed to the external grader @@ -657,13 +655,21 @@ class OpenEndedModule(): """ return self._max_score + def get_score_value(self): + """ + Returns the last score in the list + """ + score = self.latest_score() + return {'score': score if score is not None else 0, + 'total': self._max_score} + def get_progress(self): ''' For now, just return last score / max_score ''' if self._max_score > 0: try: - return Progress(self.get_score()['score'], self._max_score) + return Progress(self.get_score_value()['score'], self._max_score) except Exception as err: log.exception("Got bad progress") return None From c3a91f59083191462ee0b74b22fd553c811ddc65 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:08:51 -0500 Subject: [PATCH 054/171] Replace score message with dictionary --- common/lib/xmodule/xmodule/open_ended_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index fbd0662a63..dcc5e2a3da 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -457,7 +457,7 @@ class OpenEndedModule(): correct: Correctness of submission (Boolean) score: Points to be assigned (numeric, can be float) """ - fail = ScoreMessage(valid=False, correct=False, points=0, msg='') + fail = {'valid' : False, 'correct' : False, 'points' : 0, 'msg' : ''} try: score_result = json.loads(score_msg) except (TypeError, ValueError): @@ -486,8 +486,7 @@ class OpenEndedModule(): correct = (score_ratio >= 0.66) #Currently ignore msg and only return feedback (which takes the place of msg) - return ScoreMessage(valid=True, correct=correct, - points=score_result['score'], msg=feedback) + return {'valid' : True, 'correct' : correct, 'points' : score_result['score'], 'msg' : feedback} def handle_ajax(self, dispatch, get, system): ''' From d02b9d9ce67484226a23786d4ce0f92253e581c1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:15:39 -0500 Subject: [PATCH 055/171] Remove correct map and place state changes in its place --- .../lib/xmodule/xmodule/open_ended_module.py | 38 ++++++------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index dcc5e2a3da..4b7dd8e95b 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -295,32 +295,17 @@ class OpenEndedModule(): def _update_score(self, score_msg, oldcmap, queuekey): log.debug(score_msg) score_msg = self._parse_score_msg(score_msg) - if not score_msg.valid: - oldcmap.set(self.answer_id, - msg = 'Invalid grader reply. Please contact the course staff.') + if not score_msg['valid']: + score_msg['msg'] = 'Invalid grader reply. Please contact the course staff.' return oldcmap correctness = 'correct' if score_msg.correct else 'incorrect' - # TODO: Find out how this is used elsewhere, if any - self.context['correct'] = correctness + self._record_latest_score(score_msg['points']) + self._record_latest_feedback(score_msg['msg']) + self.state=self.POST_ASSESSMENT - # Replace 'oldcmap' with new grading results if queuekey matches. If queuekey - # does not match, we keep waiting for the score_msg whose key actually matches - if oldcmap.is_right_queuekey(self.answer_id, queuekey): - # Sanity check on returned points - points = score_msg.points - if points < 0: - points = 0 - - # Queuestate is consumed, so reset it to None - oldcmap.set(self.answer_id, npoints=points, correctness=correctness, - msg = score_msg.msg.replace(' ', ' '), queuestate=None) - else: - log.debug('OpenEndedResponse: queuekey {0} does not match for answer_id={1}.'.format( - queuekey, self.answer_id)) - - return oldcmap + return True def get_answers(self): @@ -602,11 +587,11 @@ class OpenEndedModule(): return None return self.history[-1].get('score') - def latest_hint(self): + def latest_feedback(self): """None if not available""" if not self.history: return None - return self.history[-1].get('hint') + return self.history[-1].get('feedback') def new_history_entry(self, answer): self.history.append({'answer': answer}) @@ -616,10 +601,10 @@ class OpenEndedModule(): history element""" self.history[-1]['score'] = score - def record_latest_hint(self, hint): + def record_latest_feedback(self, feedback): """Assumes that state is right, so we're adding a score to the latest history element""" - self.history[-1]['hint'] = hint + self.history[-1]['feedback'] = feedback def _allow_reset(self): """Can the module be reset?""" @@ -699,8 +684,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): { 'rubric': 'some-html', 'prompt': 'some-html', - 'submitmessage': 'some-html' - 'hintprompt': 'some-html' + 'oeparam': 'some-html' } """ From a6c273bd05160e415cc1d939194713f2ce7909c5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:18:07 -0500 Subject: [PATCH 056/171] More informative dictionary keys --- .../lib/xmodule/xmodule/open_ended_module.py | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 4b7dd8e95b..38180ebb5b 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -296,13 +296,10 @@ class OpenEndedModule(): log.debug(score_msg) score_msg = self._parse_score_msg(score_msg) if not score_msg['valid']: - score_msg['msg'] = 'Invalid grader reply. Please contact the course staff.' - return oldcmap + score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' - correctness = 'correct' if score_msg.correct else 'incorrect' - - self._record_latest_score(score_msg['points']) - self._record_latest_feedback(score_msg['msg']) + self._record_latest_score(score_msg['score']) + self._record_latest_feedback(score_msg['feedback']) self.state=self.POST_ASSESSMENT return True @@ -465,13 +462,12 @@ class OpenEndedModule(): self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] - # HACK: for now, just assume it's correct if you got more than 2/3. - # Also assumes that score_result['score'] is an integer. - score_ratio = int(score_result['score']) / float(self.max_score) - correct = (score_ratio >= 0.66) + return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} - #Currently ignore msg and only return feedback (which takes the place of msg) - return {'valid' : True, 'correct' : correct, 'points' : score_result['score'], 'msg' : feedback} + def is_submission_correct(self, score): + score_ratio = int(score) / float(self.max_score) + correct = (score_ratio >= 0.66) + return correct def handle_ajax(self, dispatch, get, system): ''' From 9cf4dc52f18779d6f644e380efa6476d6dbee34e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:20:57 -0500 Subject: [PATCH 057/171] Fix passing of system object --- common/lib/xmodule/xmodule/open_ended_module.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 38180ebb5b..defbd17ad3 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -292,9 +292,9 @@ class OpenEndedModule(): 'time': qtime,} return True - def _update_score(self, score_msg, oldcmap, queuekey): + def _update_score(self, score_msg, queuekey, system): log.debug(score_msg) - score_msg = self._parse_score_msg(score_msg) + score_msg = self._parse_score_msg(score_msg, system) if not score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' @@ -402,7 +402,7 @@ class OpenEndedModule(): return u"\n".join([feedback_list_part1,feedback_list_part2]) - def _format_feedback(self, response_items): + def _format_feedback(self, response_items, system): """ Input: Dictionary called feedback. Must contain keys seen below. @@ -413,10 +413,10 @@ class OpenEndedModule(): feedback = self._convert_longform_feedback_to_html(response_items) if not response_items['success']: - return self.system.render_template("open_ended_error.html", + return system.render_template("open_ended_error.html", {'errors' : feedback}) - feedback_template = self.system.render_template("open_ended_feedback.html", { + feedback_template = system.render_template("open_ended_feedback.html", { 'grader_type': response_items['grader_type'], 'score': "{0} / {1}".format(response_items['score'], self.max_score), 'feedback': feedback, @@ -425,7 +425,7 @@ class OpenEndedModule(): return feedback_template - def _parse_score_msg(self, score_msg): + def _parse_score_msg(self, score_msg, system): """ Grader reply is a JSON-dump of the following dict { 'correct': True/False, @@ -458,7 +458,7 @@ class OpenEndedModule(): .format(tag)) return fail - feedback = self._format_feedback(score_result) + feedback = self._format_feedback(score_result, system) self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] From 507457dc59aab7d0d7af6d377332ee28832e81c8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:22:14 -0500 Subject: [PATCH 058/171] Fix function naming --- common/lib/xmodule/xmodule/open_ended_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index defbd17ad3..febc74895a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -298,8 +298,8 @@ class OpenEndedModule(): if not score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' - self._record_latest_score(score_msg['score']) - self._record_latest_feedback(score_msg['feedback']) + self.record_latest_score(score_msg['score']) + self.record_latest_feedback(score_msg['feedback']) self.state=self.POST_ASSESSMENT return True From 3a6c660b522bca09926ce1f08d0c7b6060b030e5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:24:30 -0500 Subject: [PATCH 059/171] Now display grader message --- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index febc74895a..919bc9a0dc 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -623,7 +623,7 @@ class OpenEndedModule(): 'cols' : 80, 'hidden' : '', 'id' : 'open_ended', - 'msg' : "", + 'msg' : self.latest_feedback(), } html = system.render_template('open_ended.html', context) From 254eae034be05c881645a4c9c7367b74c7c8824f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:47:00 -0500 Subject: [PATCH 060/171] Add in support for passing problem type to javascript --- .../js/src/combinedopenended/display.coffee | 39 +++++++++++++++++-- .../lib/xmodule/xmodule/open_ended_module.py | 3 +- .../xmodule/xmodule/self_assessment_module.py | 3 +- lms/templates/open_ended.html | 7 +--- lms/templates/self_assessment_hint.html | 2 +- lms/templates/self_assessment_prompt.html | 2 +- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 08c7aafa4d..b0eed2e896 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -25,6 +25,7 @@ class @CombinedOpenEnded @message_wrapper = @$('.message-wrapper') @submit_button = @$('.submit-button') @child_state = @el.data('state') + @child_type = @el.data('child-type') @open_ended_child= @$('.open-ended-child') @@ -54,7 +55,7 @@ class @CombinedOpenEnded @submit_button.click @save_assessment else if @child_state == 'post_assessment' @answer_area.attr("disabled", true) - @submit_button.prop('value', 'Submit hint') + @submit_button.prop('value', 'Submit post-assessment') @submit_button.click @save_hint else if @child_state == 'done' @answer_area.attr("disabled", true) @@ -74,7 +75,8 @@ class @CombinedOpenEnded @assessment = @$('select.assessment') find_hint_elements: -> - @hint_area = @$('textarea.hint') + @hint_area = @$('textarea.post_assessment') + @hint_box = @$('') save_answer: (event) => event.preventDefault() @@ -118,7 +120,7 @@ class @CombinedOpenEnded if @child_state == 'post_assessment' data = {'hint' : @hint_area.val()} - $.postWithPrefix "#{@ajax_url}/save_hint", data, (response) => + $.postWithPrefix "#{@ajax_url}/save_post_assessment", data, (response) => if response.success @message_wrapper.html(response.message_html) @child_state = 'done' @@ -164,4 +166,33 @@ class @CombinedOpenEnded else @errors_area.html(response.error) else - @errors_area.html('Problem state got out of sync. Try reloading the page.') \ No newline at end of file + @errors_area.html('Problem state got out of sync. Try reloading the page.') + + message_post: => + Logger.log 'message_post', @answers + + fd = new FormData() + feedback = @$('section.evaluation textarea.feedback-on-feedback')[0].value + submission_id = $('div.external-grader-message div.submission_id')[0].innerHTML + grader_id = $('div.external-grader-message div.grader_id')[0].innerHTML + score = $(".evaluation-scoring input:radio[name='evaluation-score']:checked").val() + fd.append('feedback', feedback) + fd.append('submission_id', submission_id) + fd.append('grader_id', grader_id) + if(!score) + @gentle_alert "You need to pick a rating before you can submit." + return + else + fd.append('score', score) + + + settings = + type: "POST" + data: fd + processData: false + contentType: false + success: (response) => + @gentle_alert response.message + @$('section.evaluation').slideToggle() + + $.ajaxWithPrefix("#{@url}/message_post", settings) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 919bc9a0dc..24b00a0126 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -485,7 +485,7 @@ class OpenEndedModule(): 'problem_reset': self.reset_problem, 'save_answer': self.save_answer, 'score_update': self.update_score, - 'message_post' : self.message_post, + 'save_post_assessment' : self.message_post, } if dispatch not in handlers: @@ -624,6 +624,7 @@ class OpenEndedModule(): 'hidden' : '', 'id' : 'open_ended', 'msg' : self.latest_feedback(), + 'child_type' : 'openended', } html = system.render_template('open_ended.html', context) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 64cf140d38..ee35ff801c 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -235,6 +235,7 @@ class SelfAssessmentModule(): 'initial_message': self.get_message_html(), 'state': self.state, 'allow_reset': self._allow_reset(), + 'child_type' : 'selfassessment', } html = system.render_template('self_assessment_prompt.html', context) @@ -281,7 +282,7 @@ class SelfAssessmentModule(): handlers = { 'save_answer': self.save_answer, 'save_assessment': self.save_assessment, - 'save_hint': self.save_hint, + 'save_post_assessment': self.save_hint, 'reset': self.reset, } diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index d682de5291..a40f0e189e 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -1,4 +1,4 @@ -
+
@@ -35,7 +35,7 @@ % endif
${msg|n} - % if state == 'done': + % if state == 'post_assessment':
Respond to Feedback @@ -53,9 +53,6 @@

Additional comments:

-
- -
% endif diff --git a/lms/templates/self_assessment_hint.html b/lms/templates/self_assessment_hint.html index 64c45b809e..1adfc69e39 100644 --- a/lms/templates/self_assessment_hint.html +++ b/lms/templates/self_assessment_hint.html @@ -2,6 +2,6 @@
${hint_prompt}
-
diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 91223fdaa1..479e42a126 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -1,5 +1,5 @@
+ data-id="${id}" data-state="${state}" data-allow_reset="${allow_reset}" data-child-type="${child_type}">
${prompt} From ca234dc900136e50981e2e150081f765c061dc86 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 14:59:11 -0500 Subject: [PATCH 061/171] Fixing message post --- .../js/src/combinedopenended/display.coffee | 14 +++++++++++--- common/lib/xmodule/xmodule/open_ended_module.py | 6 +++--- lms/templates/open_ended.html | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index b0eed2e896..30b2e6b965 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -56,7 +56,10 @@ class @CombinedOpenEnded else if @child_state == 'post_assessment' @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') - @submit_button.click @save_hint + if @child_type=="selfassessment" + @submit_button.click @save_hint + else + @submit_button.click @message_post else if @child_state == 'done' @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @@ -76,7 +79,6 @@ class @CombinedOpenEnded find_hint_elements: -> @hint_area = @$('textarea.post_assessment') - @hint_box = @$('') save_answer: (event) => event.preventDefault() @@ -195,4 +197,10 @@ class @CombinedOpenEnded @gentle_alert response.message @$('section.evaluation').slideToggle() - $.ajaxWithPrefix("#{@url}/message_post", settings) \ No newline at end of file + $.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings) + + gentle_alert: (msg) => + if @el.find('.open-ended-alert').length + @el.find('.open-ended-alert').remove() + alert_elem = "
" + msg + "
" + @el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 24b00a0126..a7d34b5636 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -204,7 +204,7 @@ class OpenEndedModule(): survey_responses=event_info['survey_responses'] for tag in ['feedback', 'submission_id', 'grader_id', 'score']: if tag not in survey_responses: - return False, "Could not find needed tag {0}".format(tag) + return {'success' : False, 'msg' : "Could not find needed tag {0}".format(tag)} try: submission_id=int(survey_responses['submission_id']) grader_id = int(survey_responses['grader_id']) @@ -214,7 +214,7 @@ class OpenEndedModule(): error_message=("Could not parse submission id, grader id, " "or feedback from message_post ajax call. Here is the message data: {0}".format(survey_responses)) log.exception(error_message) - return False, "There was an error saving your feedback. Please contact course staff." + return {'success' : False, 'msg' : "There was an error saving your feedback. Please contact course staff."} qinterface = system.xqueue['interface'] qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) @@ -248,7 +248,7 @@ class OpenEndedModule(): if error: success=False - return success, "Successfully submitted your feedback." + return {'success' : success, 'msg' : "Successfully submitted your feedback."} def get_score(self, submission, system): diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index a40f0e189e..fae2b45041 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -57,4 +57,5 @@
% endif
+
From 45036cb6544b829aad147ec642c6dc74c3269df8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:01:48 -0500 Subject: [PATCH 062/171] Message post queuekey change --- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index a7d34b5636..5bee2e67a6 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -221,7 +221,7 @@ class OpenEndedModule(): anonymous_student_id = system.anonymous_student_id queuekey = xqueue_interface.make_hashkey(str(system.seed) + qtime + anonymous_student_id + - self.answer_id) + str(len(self.history))) xheader = xqueue_interface.make_xheader( lms_callback_url=system.xqueue['callback_url'], From d2ef85cce54058de7fbb70807fc1427231e4c35b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:03:07 -0500 Subject: [PATCH 063/171] Finalize submission after message post --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++++ common/lib/xmodule/xmodule/open_ended_module.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 30b2e6b965..d02db0b932 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -196,6 +196,10 @@ class @CombinedOpenEnded success: (response) => @gentle_alert response.message @$('section.evaluation').slideToggle() + @message_wrapper.html(response.message_html) + @child_state = 'done' + @allow_reset = response.allow_reset + @rebind() $.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 5bee2e67a6..96cc87da3a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -248,6 +248,8 @@ class OpenEndedModule(): if error: success=False + self.state=self.DONE + return {'success' : success, 'msg' : "Successfully submitted your feedback."} def get_score(self, submission, system): From 79fdb79ed4fb0189023f26d1c46758c7fd789d5c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:17:06 -0500 Subject: [PATCH 064/171] Fixing states and html display --- .../xmodule/xmodule/combined_open_ended_module.py | 2 +- common/lib/xmodule/xmodule/open_ended_module.py | 12 ++++++------ 2 files changed, 7 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 f5014af948..cb05074b34 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -131,7 +131,7 @@ class CombinedOpenEndedModule(XModule): self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) - current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + current_task_state = ('{"state": "initial", "version": 1, "max_score": ' + str(self._max_score) + ', ' + '"attempts": 0, "history": [{"answer": "' + str(last_response) + '"}]}') {"state": "done", "version": 1, "max_score": 1, "attempts": 1, "history": [{"answer": "gdgddg", "score": 0, "hint": "dfdfdf"}]} self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 96cc87da3a..2cc436a808 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -576,20 +576,20 @@ class OpenEndedModule(): def latest_answer(self): """None if not available""" if not self.history: - return None - return self.history[-1].get('answer') + return "" + return self.history[-1].get('answer', "") def latest_score(self): """None if not available""" if not self.history: - return None - return self.history[-1].get('score') + return "" + return self.history[-1].get('score', "") def latest_feedback(self): """None if not available""" if not self.history: - return None - return self.history[-1].get('feedback') + return "" + return self.history[-1].get('feedback', "") def new_history_entry(self, answer): self.history.append({'answer': answer}) From 85776fbe3064bfd7cd7a328653a702c9fe608905 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:23:41 -0500 Subject: [PATCH 065/171] Fix reset in open ended --- .../lib/xmodule/xmodule/open_ended_module.py | 21 ++++++++++++++++++- .../xmodule/xmodule/self_assessment_module.py | 1 - 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 2cc436a808..7127ef2388 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -484,7 +484,6 @@ class OpenEndedModule(): log.debug(get) handlers = { 'problem_get': self.get_problem, - 'problem_reset': self.reset_problem, 'save_answer': self.save_answer, 'score_update': self.update_score, 'save_post_assessment' : self.message_post, @@ -509,6 +508,16 @@ class OpenEndedModule(): self.change_state(self.INITIAL) return {'success': True} + def out_of_sync_error(self, get, msg=''): + """ + return dict out-of-sync error message, and also log. + """ + log.warning("Assessment module state out sync. state: %r, get: %r. %s", + self.state, get, msg) + return {'success': False, + 'error': 'The problem state got out-of-sync'} + + def save_answer(self, get, system): if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and @@ -658,6 +667,16 @@ class OpenEndedModule(): return None return None + def reset(self, system): + """ + If resetting is allowed, reset the state. + + Returns {'success': bool, 'error': msg} + (error only present if not success) + """ + self.change_state(self.INITIAL) + return {'success': True} + class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index ee35ff801c..925083c97a 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -283,7 +283,6 @@ class SelfAssessmentModule(): 'save_answer': self.save_answer, 'save_assessment': self.save_assessment, 'save_post_assessment': self.save_hint, - 'reset': self.reset, } if dispatch not in handlers: From f3ca5f456fc94bb2a9305374535722cbc84fad7a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:32:56 -0500 Subject: [PATCH 066/171] Save answer based on history --- common/lib/xmodule/xmodule/open_ended_module.py | 7 +++++-- lms/templates/open_ended.html | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 7127ef2388..ad338203f1 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -532,7 +532,11 @@ class OpenEndedModule(): return self.out_of_sync_error(get) # add new history element with answer and empty score and hint. - self.new_history_entry(get['student_answer']) + if(len(self.history)>0): + if(len(self.history[-1].keys())>1): + self.new_history_entry(get['student_answer']) + else: + get['student_answer']=self.latest_answer() self.get_score(get['student_answer'], system) self.change_state(self.ASSESSING) @@ -632,7 +636,6 @@ class OpenEndedModule(): 'allow_reset': self._allow_reset(), 'rows' : 30, 'cols' : 80, - 'hidden' : '', 'id' : 'open_ended', 'msg' : self.latest_feedback(), 'child_type' : 'openended', diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index fae2b45041..fbf8d74d16 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -4,11 +4,7 @@
${prompt|n} - +
% if state == 'initial': From f131b8673d7c6f93dd404d8d43e721f042c0bdea Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 15:54:53 -0500 Subject: [PATCH 067/171] Handle multiple chained submissions --- .../xmodule/xmodule/combined_open_ended_module.py | 8 +++----- common/lib/xmodule/xmodule/open_ended_module.py | 15 ++++++++++----- .../lib/xmodule/xmodule/self_assessment_module.py | 3 +++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index cb05074b34..d08eef7253 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -115,8 +115,7 @@ class CombinedOpenEndedModule(XModule): elif current_task_state is None and self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "history": [{"answer": "' + str(last_response) + '"}]}') - {"state": "done", "version": 1, "max_score": 1, "attempts": 1, "history": [{"answer": "gdgddg", "score": 0, "hint": "dfdfdf"}]} + '"attempts": 0, "created": True, "history": [{"answer": "' + str(last_response) + '"}]}') self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING @@ -131,9 +130,8 @@ class CombinedOpenEndedModule(XModule): self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) - current_task_state = ('{"state": "initial", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "history": [{"answer": "' + str(last_response) + '"}]}') - {"state": "done", "version": 1, "max_score": 1, "attempts": 1, "history": [{"answer": "gdgddg", "score": 0, "hint": "dfdfdf"}]} + current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + '"attempts": 0, "created": True, "history": [{"answer": "' + str(last_response) + '"}]}') self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index ad338203f1..02ce31cea0 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -116,6 +116,14 @@ class OpenEndedModule(): self.state = instance_state.get('state', 'initial') + self.created = instance_state.get('created', False) + + if self.created and self.state == self.ASSESSING: + self.created=False + self.get_score(self.latest_answer(), system) + + self.created=False + self.attempts = instance_state.get('attempts', 0) self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) @@ -532,11 +540,7 @@ class OpenEndedModule(): return self.out_of_sync_error(get) # add new history element with answer and empty score and hint. - if(len(self.history)>0): - if(len(self.history[-1].keys())>1): - self.new_history_entry(get['student_answer']) - else: - get['student_answer']=self.latest_answer() + self.new_history_entry(get['student_answer']) self.get_score(get['student_answer'], system) self.change_state(self.ASSESSING) @@ -583,6 +587,7 @@ class OpenEndedModule(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, + 'created' : self.created, } return json.dumps(state) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 925083c97a..56178acee4 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -107,6 +107,8 @@ class SelfAssessmentModule(): # Scores are on scale from 0 to max_score self.history = instance_state.get('history', []) + self.created = instance_state.get('created', False) + self.state = instance_state.get('state', 'initial') self.attempts = instance_state.get('attempts', 0) @@ -489,6 +491,7 @@ class SelfAssessmentModule(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, + 'created' : self.created, } return json.dumps(state) From d3ccc64846ebd33ffc43992d2d7f507f5a8b9dfb Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 16:06:41 -0500 Subject: [PATCH 068/171] Remove some unneeded log statements --- .../xmodule/combined_open_ended_module.py | 6 +++--- .../lib/xmodule/xmodule/open_ended_module.py | 18 ++++++++---------- .../xmodule/xmodule/self_assessment_module.py | 4 ++-- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index d08eef7253..a5fa059a37 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -101,7 +101,6 @@ class CombinedOpenEndedModule(XModule): if self.state in [self.ASSESSING, self.DONE]: current_task_state=self.task_states[len(self.task_states)-1] - log.debug(self.task_states) self.current_task_xml=self.task_xml[self.current_task_number] current_task_type=self.get_tag_name(self.current_task_xml) @@ -115,7 +114,7 @@ class CombinedOpenEndedModule(XModule): elif current_task_state is None and self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": True, "history": [{"answer": "' + str(last_response) + '"}]}') + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING @@ -131,7 +130,8 @@ class CombinedOpenEndedModule(XModule): elif current_task_state is None and self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": True, "history": [{"answer": "' + str(last_response) + '"}]}') + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') + log.debug(current_task_state) self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 02ce31cea0..d3d04fe2ec 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -104,6 +104,7 @@ class OpenEndedModule(): # Load instance state if instance_state is not None: + log.debug(instance_state) instance_state = json.loads(instance_state) else: instance_state = {} @@ -116,13 +117,7 @@ class OpenEndedModule(): self.state = instance_state.get('state', 'initial') - self.created = instance_state.get('created', False) - - if self.created and self.state == self.ASSESSING: - self.created=False - self.get_score(self.latest_answer(), system) - - self.created=False + self.created = instance_state.get('created', "False") self.attempts = instance_state.get('attempts', 0) self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) @@ -152,6 +147,11 @@ class OpenEndedModule(): self._parse(oeparam, prompt, rubric, system) + if self.created=="True" and self.state == self.ASSESSING: + self.created="False" + self.get_score(self.latest_answer(), system) + self.created="False" + def _parse(self, oeparam, prompt, rubric, system): ''' Parse OpenEndedResponse XML: @@ -303,7 +303,6 @@ class OpenEndedModule(): return True def _update_score(self, score_msg, queuekey, system): - log.debug(score_msg) score_msg = self._parse_score_msg(score_msg, system) if not score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' @@ -489,7 +488,6 @@ class OpenEndedModule(): 'progress' : 'none'/'in_progress'/'done', } ''' - log.debug(get) handlers = { 'problem_get': self.get_problem, 'save_answer': self.save_answer, @@ -587,7 +585,7 @@ class OpenEndedModule(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, - 'created' : self.created, + 'created' : "False", } return json.dumps(state) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 56178acee4..64edb2bb8a 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -107,7 +107,7 @@ class SelfAssessmentModule(): # Scores are on scale from 0 to max_score self.history = instance_state.get('history', []) - self.created = instance_state.get('created', False) + self.created = instance_state.get('created', "False") self.state = instance_state.get('state', 'initial') @@ -491,7 +491,7 @@ class SelfAssessmentModule(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, - 'created' : self.created, + 'created' : "False", } return json.dumps(state) From a39ce505d8486167234fb798837c3cfff11c5720 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 16:34:55 -0500 Subject: [PATCH 069/171] Fix loading for responses after attempt 1 --- .../xmodule/combined_open_ended_module.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index a5fa059a37..a94eb1f170 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -101,7 +101,6 @@ class CombinedOpenEndedModule(XModule): if self.state in [self.ASSESSING, self.DONE]: current_task_state=self.task_states[len(self.task_states)-1] - self.current_task_xml=self.task_xml[self.current_task_number] current_task_type=self.get_tag_name(self.current_task_xml) if current_task_type=="selfassessment": @@ -119,7 +118,14 @@ class CombinedOpenEndedModule(XModule): self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING else: + if self.current_task_number>0: + last_response, last_score=self.get_last_response(self.current_task_number-1) + loaded_task_state=json.loads(current_task_state) + loaded_task_state['state']=self.ASSESSING + loaded_task_state['created'] = "True" + loaded_task_state['history'].append({'answer' : last_response}) self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + elif current_task_type=="openended": self.current_task_descriptor= open_ended_module.OpenEndedDescriptor(self.system) self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) @@ -131,14 +137,19 @@ class CombinedOpenEndedModule(XModule): last_response, last_score=self.get_last_response(self.current_task_number-1) current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - log.debug(current_task_state) self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING else: + if self.current_task_number>0: + last_response, last_score=self.get_last_response(self.current_task_number-1) + loaded_task_state=json.loads(current_task_state) + loaded_task_state['state']=self.ASSESSING + loaded_task_state['created'] = "True" + loaded_task_state['history'].append({'answer' : last_response}) self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) - + log.debug(self.current_task.get_instance_state()) return True def get_html(self): @@ -231,7 +242,7 @@ class CombinedOpenEndedModule(XModule): return json.dumps(d,cls=ComplexEncoder) def next_problem(self, get): - self.setup_next_task() + self.update_task_states() return {'success' : True} def reset(self, get): From 54db9fd03ce78964d05a7185add5352d615d4063 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 16:47:52 -0500 Subject: [PATCH 070/171] Fix state tracking? --- .../xmodule/combined_open_ended_module.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index a94eb1f170..006014700e 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -98,8 +98,8 @@ class CombinedOpenEndedModule(XModule): def setup_next_task(self): current_task_state=None - if self.state in [self.ASSESSING, self.DONE]: - current_task_state=self.task_states[len(self.task_states)-1] + if len(self.task_states)>self.current_task_number: + current_task_state=self.task_states[self.current_task_number] self.current_task_xml=self.task_xml[self.current_task_number] current_task_type=self.get_tag_name(self.current_task_xml) @@ -121,9 +121,11 @@ class CombinedOpenEndedModule(XModule): if self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) loaded_task_state=json.loads(current_task_state) - loaded_task_state['state']=self.ASSESSING - loaded_task_state['created'] = "True" - loaded_task_state['history'].append({'answer' : last_response}) + 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}) + current_task_state=json.dumps(loaded_task_state) self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) elif current_task_type=="openended": @@ -144,9 +146,11 @@ class CombinedOpenEndedModule(XModule): if self.current_task_number>0: last_response, last_score=self.get_last_response(self.current_task_number-1) loaded_task_state=json.loads(current_task_state) - loaded_task_state['state']=self.ASSESSING - loaded_task_state['created'] = "True" - loaded_task_state['history'].append({'answer' : last_response}) + 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}) + current_task_state=json.dumps(loaded_task_state) self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) log.debug(self.current_task.get_instance_state()) From 332ad89b6ab34b56c55ba3f43088863a13cc0d7b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 17:01:41 -0500 Subject: [PATCH 071/171] Streamline modules --- .../xmodule/combined_open_ended_module.py | 92 +++++++++---------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 006014700e..db69c2dae3 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -95,6 +95,30 @@ class CombinedOpenEndedModule(XModule): tag=etree.fromstring(xml).tag return tag + def overwrite_state(self, current_task_state): + last_response, last_score=self.get_last_response(self.current_task_number-1) + 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}) + current_task_state=json.dumps(loaded_task_state) + return current_task_state + + def child_modules(self): + child_modules={ + 'openended' : open_ended_module.OpenEndedModule, + 'selfassessment' : self_assessment_module.SelfAssessmentModule, + } + child_descriptors={ + 'openended' : open_ended_module.OpenEndedDescriptor, + 'selfassessment' : self_assessment_module.SelfAssessmentDescriptor, + } + children={ + 'modules' : child_modules, + 'descriptors' : child_descriptors, + } + return children def setup_next_task(self): current_task_state=None @@ -103,55 +127,26 @@ class CombinedOpenEndedModule(XModule): self.current_task_xml=self.task_xml[self.current_task_number] current_task_type=self.get_tag_name(self.current_task_xml) - if current_task_type=="selfassessment": - self.current_task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) - self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) - if current_task_state is None and self.current_task_number==0: - self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) - self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING - elif current_task_state is None and self.current_task_number>0: - last_response, last_score=self.get_last_response(self.current_task_number-1) - current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) - self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING - else: - if self.current_task_number>0: - last_response, last_score=self.get_last_response(self.current_task_number-1) - 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}) - current_task_state=json.dumps(loaded_task_state) - self.current_task=self_assessment_module.SelfAssessmentModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) - elif current_task_type=="openended": - self.current_task_descriptor= open_ended_module.OpenEndedDescriptor(self.system) - self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) - if current_task_state is None and self.current_task_number==0: - self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) - self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING - elif current_task_state is None and self.current_task_number>0: - last_response, last_score=self.get_last_response(self.current_task_number-1) - current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) - self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING - else: - if self.current_task_number>0: - last_response, last_score=self.get_last_response(self.current_task_number-1) - 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}) - current_task_state=json.dumps(loaded_task_state) - self.current_task=open_ended_module.OpenEndedModule(self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + children=self.child_modules() + + self.current_task_descriptor=children['descriptors'][current_task_type](self.system) + self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) + if current_task_state is None and self.current_task_number==0: + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING + elif current_task_state is None and self.current_task_number>0: + last_response, last_score=self.get_last_response(self.current_task_number-1) + current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + self.task_states.append(self.current_task.get_instance_state()) + self.state=self.ASSESSING + else: + if self.current_task_number>0: + current_task_state=self.overwrite_state(current_task_state) + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) log.debug(self.current_task.get_instance_state()) return True @@ -270,6 +265,7 @@ class CombinedOpenEndedModule(XModule): self.setup_next_task() self.current_task.reset(self.system) self.current_task_number=0 + self.setup_next_task() return {'success': True} From 2fff83f8813c7a731133dd34f7c177340c6494a3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 17:03:19 -0500 Subject: [PATCH 072/171] More streamlining --- .../xmodule/combined_open_ended_module.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index db69c2dae3..22ac3aa0b7 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -180,18 +180,13 @@ class CombinedOpenEndedModule(XModule): task_xml=self.task_xml[task_number] task_type=self.get_tag_name(task_xml) - if task_type=="selfassessment": - task_descriptor=self_assessment_module.SelfAssessmentDescriptor(self.system) - task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) - task=self_assessment_module.SelfAssessmentModule(self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) - last_response=task.latest_answer() - last_score = task.latest_score() - elif task_type=="openended": - task_descriptor=open_ended_module.OpenEndedDescriptor(self.system) - task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) - task=open_ended_module.OpenEndedModule(self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) - last_response=task.latest_answer() - last_score = task.latest_score() + children=self.child_modules() + + task_descriptor=children['descriptors'][task_type](self.system) + task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) + task=children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) + last_response=task.latest_answer() + last_score = task.latest_score() return last_response, last_score From 1f1fdc2360bc736faa2cb00805ac8dec41dbbe22 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 17:04:16 -0500 Subject: [PATCH 073/171] Add back in support for reset --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 6 +++--- 1 file changed, 3 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 22ac3aa0b7..9384eb9d27 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -120,7 +120,7 @@ class CombinedOpenEndedModule(XModule): } return children - def setup_next_task(self): + def setup_next_task(self, reset=False): current_task_state=None if len(self.task_states)>self.current_task_number: current_task_state=self.task_states[self.current_task_number] @@ -144,7 +144,7 @@ class CombinedOpenEndedModule(XModule): self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING else: - if self.current_task_number>0: + if self.current_task_number>0 and not reset: current_task_state=self.overwrite_state(current_task_state) self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) @@ -257,7 +257,7 @@ class CombinedOpenEndedModule(XModule): self.state=self.INITIAL for i in xrange(0,len(self.task_xml)): self.current_task_number=i - self.setup_next_task() + self.setup_next_task(reset=True) self.current_task.reset(self.system) self.current_task_number=0 self.setup_next_task() From ccefeccecc65f92ee1d04d407b9a1a8402a2f755 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 17:28:50 -0500 Subject: [PATCH 074/171] Several bug fixes --- .../xmodule/xmodule/combined_open_ended_module.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 9384eb9d27..7b20bf053c 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -27,7 +27,7 @@ log = logging.getLogger("mitx.courseware") # Set the default number of max attempts. Should be 1 for production # Set higher for debugging/testing # attempts specified in xml definition overrides this. -MAX_ATTEMPTS = 1 +MAX_ATTEMPTS = 10000 # Set maximum available number of points. # Overriden by max_score specified in xml. @@ -149,6 +149,7 @@ class CombinedOpenEndedModule(XModule): self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) log.debug(self.current_task.get_instance_state()) + log.debug(self.get_instance_state()) return True def get_html(self): @@ -192,15 +193,15 @@ class CombinedOpenEndedModule(XModule): def update_task_states(self): changed=False - self.task_states[len(self.task_states)-1] = self.current_task.get_instance_state() - current_task_state=json.loads(self.task_states[len(self.task_states)-1]) + 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: 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.current_task_number=self.current_task_number-1 + self.current_task_number=len(self.task_xml)-1 else: - self.state=self.INTERMEDIATE_DONE + self.state=self.INITIAL changed=True self.setup_next_task() return changed @@ -259,6 +260,7 @@ class CombinedOpenEndedModule(XModule): 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.setup_next_task() return {'success': True} From 9ba57ad5df27810f485f5b45175c7e3d61aeee38 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 17:42:42 -0500 Subject: [PATCH 075/171] Add in correctness display for open ended response --- common/lib/xmodule/xmodule/open_ended_module.py | 8 +++++++- lms/templates/open_ended.html | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index d3d04fe2ec..2f8e49e868 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -629,7 +629,12 @@ class OpenEndedModule(): if self.state != self.INITIAL: latest = self.latest_answer() previous_answer = latest if latest is not None else self.initial_display + feedback = self.latest_feedback() + score= self.latest_score() + correct = "correct" if is_submission_correct else 'incorrect' else: + feedback="" + correct="" previous_answer = self.initial_display context = { @@ -640,8 +645,9 @@ class OpenEndedModule(): 'rows' : 30, 'cols' : 80, 'id' : 'open_ended', - 'msg' : self.latest_feedback(), + 'msg' : feedback, 'child_type' : 'openended', + 'correct' : correct, } html = system.render_template('open_ended.html', context) diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index fbf8d74d16..d98e10276d 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -9,9 +9,9 @@
% if state == 'initial': Unanswered - % elif state == 'done': + % elif state == 'done' and correct=='incorrect': Correct - % elif state == 'incorrect': + % elif state == 'done' and correct=='correct': Incorrect % elif state == 'assessing': Submitted for grading From 41ba6dde3543505cbcaca7c7848a745a67fc896d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 18:47:52 -0500 Subject: [PATCH 076/171] Make things looks slightly prettier --- .../xmodule/combined_open_ended_module.py | 2 + .../css/combinedopenended/display.scss | 510 ++++++++++++++++++ .../js/src/combinedopenended/display.coffee | 9 + .../lib/xmodule/xmodule/open_ended_module.py | 10 +- lms/templates/open_ended.html | 10 +- 5 files changed, 531 insertions(+), 10 deletions(-) create mode 100644 common/lib/xmodule/xmodule/css/combinedopenended/display.scss diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 7b20bf053c..4fcff80214 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -47,6 +47,8 @@ class CombinedOpenEndedModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} js_module_name = "CombinedOpenEnded" + css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]} + def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss new file mode 100644 index 0000000000..73fa4018d2 --- /dev/null +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -0,0 +1,510 @@ +h2 { + margin-top: 0; + margin-bottom: 15px; + + &.problem-header { + section.staff { + margin-top: 30px; + font-size: 80%; + } + } + + @media print { + display: block; + width: auto; + border-right: 0; + } +} + +.inline-error { + color: darken($error-red, 10%); +} + +section.open-ended-child { + @media print { + display: block; + width: auto; + padding: 0; + + canvas, img { + page-break-inside: avoid; + } + } + + .inline { + display: inline; + } + + ol.enumerate { + li { + &:before { + content: " "; + display: block; + height: 0; + visibility: hidden; + } + } + } + + .solution-span { + > span { + margin: 20px 0; + display: block; + border: 1px solid #ddd; + padding: 9px 15px 20px; + background: #FFF; + position: relative; + @include box-shadow(inset 0 0 0 1px #eee); + @include border-radius(3px); + + &:empty { + display: none; + } + } + } + + div { + p { + &.answer { + margin-top: -2px; + } + &.status { + text-indent: -9999px; + margin: 8px 0 0 10px; + } + } + + &.unanswered { + p.status { + @include inline-block(); + background: url('../images/unanswered-icon.png') center center no-repeat; + height: 14px; + width: 14px; + } + } + + &.correct, &.ui-icon-check { + p.status { + @include inline-block(); + background: url('../images/correct-icon.png') center center no-repeat; + height: 20px; + width: 25px; + } + + input { + border-color: green; + } + } + + &.processing { + p.status { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + width: 20px; + } + + input { + border-color: #aaa; + } + } + + &.incorrect, &.ui-icon-close { + p.status { + @include inline-block(); + background: url('../images/incorrect-icon.png') center center no-repeat; + height: 20px; + width: 20px; + text-indent: -9999px; + } + + input { + border-color: red; + } + } + + > span { + display: block; + margin-bottom: lh(.5); + } + + p.answer { + @include inline-block(); + margin-bottom: 0; + margin-left: 10px; + + &:before { + content: "Answer: "; + font-weight: bold; + display: inline; + + } + &:empty { + &:before { + display: none; + } + } + } + + span { + &.unanswered, &.ui-icon-bullet { + @include inline-block(); + background: url('../images/unanswered-icon.png') center center no-repeat; + height: 14px; + position: relative; + top: 4px; + width: 14px; + } + + &.processing, &.ui-icon-processing { + @include inline-block(); + background: url('../images/spinner.gif') center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; + } + + &.correct, &.ui-icon-check { + @include inline-block(); + background: url('../images/correct-icon.png') center center no-repeat; + height: 20px; + position: relative; + top: 6px; + width: 25px; + } + + &.incorrect, &.ui-icon-close { + @include inline-block(); + background: url('../images/incorrect-icon.png') center center no-repeat; + height: 20px; + width: 20px; + position: relative; + top: 6px; + } + } + + .reload + { + float:right; + margin: 10px; + } + + + .grader-status { + padding: 9px; + background: #F6F6F6; + border: 1px solid #ddd; + border-top: 0; + margin-bottom: 20px; + @include clearfix; + + span { + text-indent: -9999px; + overflow: hidden; + display: block; + float: left; + margin: -7px 7px 0 0; + } + + .grading { + background: url('../images/info-icon.png') left center no-repeat; + padding-left: 25px; + text-indent: 0px; + margin: 0px 7px 0 0; + } + + p { + line-height: 20px; + text-transform: capitalize; + margin-bottom: 0; + float: left; + } + + &.file { + background: #FFF; + margin-top: 20px; + padding: 20px 0 0 0; + + border: { + top: 1px solid #eee; + right: 0; + bottom: 0; + left: 0; + } + + p.debug { + display: none; + } + + input { + float: left; + } + } + + } + .evaluation { + p { + margin-bottom: 4px; + } + } + + + .feedback-on-feedback { + height: 100px; + margin-right: 20px; + } + + .evaluation-response { + header { + text-align: right; + a { + font-size: .85em; + } + } + } + + .evaluation-scoring { + .scoring-list { + list-style-type: none; + margin-left: 3px; + + li { + &:first-child { + margin-left: 0px; + } + display:inline; + margin-left: 50px; + + label { + font-size: .9em; + } + + } + } + + } + .submit-message-container { + margin: 10px 0px ; + } + } + + form.option-input { + margin: -10px 0 20px; + padding-bottom: 20px; + + select { + margin-right: flex-gutter(); + } + } + + ul { + list-style: disc outside none; + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + } + + ol { + list-style: decimal outside none; + margin-bottom: lh(); + margin-left: .75em; + margin-left: .75rem; + } + + dl { + line-height: 1.4em; + } + + dl dt { + font-weight: bold; + } + + dl dd { + margin-bottom: 0; + } + + dd { + margin-left: .5em; + margin-left: .5rem; + } + + li { + line-height: 1.4em; + margin-bottom: lh(.5); + + &:last-child { + margin-bottom: 0; + } + } + + p { + margin-bottom: lh(); + } + + hr { + background: #ddd; + border: none; + clear: both; + color: #ddd; + float: none; + height: 1px; + margin: 0 0 .75rem; + width: 100%; + } + + .hidden { + display: none; + visibility: hidden; + } + + #{$all-text-inputs} { + display: inline; + width: auto; + } + + section.action { + margin-top: 20px; + + input.save { + @extend .blue-button; + } + + .submission_feedback { + // background: #F3F3F3; + // border: 1px solid #ddd; + // @include border-radius(3px); + // padding: 8px 12px; + // margin-top: 10px; + @include inline-block; + font-style: italic; + margin: 8px 0 0 10px; + color: #777; + -webkit-font-smoothing: antialiased; + } + } + + .detailed-solution { + > p:first-child { + font-size: 0.9em; + font-weight: bold; + font-style: normal; + text-transform: uppercase; + color: #AAA; + } + + p:last-child { + margin-bottom: 0; + } + } + + div.capa_alert { + padding: 8px 12px; + border: 1px solid #EBE8BF; + border-radius: 3px; + background: #FFFCDD; + font-size: 0.9em; + margin-top: 10px; + } + + div.capa_reset { + padding: 25px; + border: 1px solid $error-red; + background-color: lighten($error-red, 25%); + border-radius: 3px; + font-size: 1em; + margin-top: 10px; + margin-bottom: 10px; + } + .capa_reset>h2 { + color: #AA0000; + } + .capa_reset li { + font-size: 0.9em; + } + + .external-grader-message { + section { + padding-left: 20px; + background-color: #FAFAFA; + color: #2C2C2C; + font-family: monospace; + font-size: 1em; + padding-top: 10px; + header { + font-size: 1.4em; + } + + .shortform { + font-weight: bold; + } + + .longform { + padding: 0px; + margin: 0px; + + .result-errors { + margin: 5px; + padding: 10px 10px 10px 40px; + background: url('../images/incorrect-icon.png') center left no-repeat; + li { + color: #B00; + } + } + + .result-output { + margin: 5px; + padding: 20px 0px 15px 50px; + border-top: 1px solid #DDD; + border-left: 20px solid #FAFAFA; + + h4 { + font-family: monospace; + font-size: 1em; + } + + dl { + margin: 0px; + } + + dt { + margin-top: 20px; + } + + dd { + margin-left: 24pt; + } + } + + .result-correct { + background: url('../images/correct-icon.png') left 20px no-repeat; + .result-actual-output { + color: #090; + } + } + + .result-incorrect { + background: url('../images/incorrect-icon.png') left 20px no-repeat; + .result-actual-output { + color: #B00; + } + } + + .markup-text{ + margin: 5px; + padding: 20px 0px 15px 50px; + border-top: 1px solid #DDD; + border-left: 20px solid #FAFAFA; + + bs { + color: #BB0000; + } + + bg { + color: #BDA046; + } + } + } + } + } +} diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index d02db0b932..694b0ae481 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -26,6 +26,8 @@ class @CombinedOpenEnded @submit_button = @$('.submit-button') @child_state = @el.data('state') @child_type = @el.data('child-type') + if @child_type="openended" + @reload_button = @$('.reload-button') @open_ended_child= @$('.open-ended-child') @@ -45,6 +47,8 @@ class @CombinedOpenEnded @reset_button.hide() @next_problem_button.hide() @hint_area.attr('disabled', false) + if @child_type=="openended" + @reload_button.hide() if @child_state == 'initial' @answer_area.attr("disabled", false) @submit_button.prop('value', 'Submit') @@ -53,7 +57,12 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit assessment') @submit_button.click @save_assessment + if @child_type == "openended" + @submit_button.hide() + @reload_button.show() else if @child_state == 'post_assessment' + if @child_type=="openended" + @reload_button.hide() @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') if @child_type=="selfassessment" diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 2f8e49e868..5bd17c220a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -474,8 +474,10 @@ class OpenEndedModule(): return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} def is_submission_correct(self, score): - score_ratio = int(score) / float(self.max_score) - correct = (score_ratio >= 0.66) + correct=False + if(isinstance(score,(int, long, float, complex))): + score_ratio = int(score) / float(self.max_score) + correct = (score_ratio >= 0.66) return correct def handle_ajax(self, dispatch, get, system): @@ -631,7 +633,7 @@ class OpenEndedModule(): previous_answer = latest if latest is not None else self.initial_display feedback = self.latest_feedback() score= self.latest_score() - correct = "correct" if is_submission_correct else 'incorrect' + correct = 'correct' if self.is_submission_correct(score) else 'incorrect' else: feedback="" correct="" @@ -649,7 +651,7 @@ class OpenEndedModule(): 'child_type' : 'openended', 'correct' : correct, } - + log.debug(context) html = system.render_template('open_ended.html', context) return html diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index d98e10276d..b29c915d53 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -3,15 +3,15 @@
${prompt|n} - +
% if state == 'initial': Unanswered - % elif state == 'done' and correct=='incorrect': + % elif state in ['done', 'post_assessment'] and correct == 'incorrect': Correct - % elif state == 'done' and correct=='correct': + % elif state in ['done', 'post_assessment'] and correct == 'correct': Incorrect % elif state == 'assessing': Submitted for grading @@ -26,9 +26,7 @@ - % if state == 'assessing': - - % endif +
${msg|n} % if state == 'post_assessment': From e3da5aeb2c7e587805e15a7f209b3e457942ab96 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 18:48:40 -0500 Subject: [PATCH 077/171] Reverse correct and incorrect --- lms/templates/open_ended.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index b29c915d53..423b2dc709 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -9,9 +9,9 @@
% if state == 'initial': Unanswered - % elif state in ['done', 'post_assessment'] and correct == 'incorrect': - Correct % elif state in ['done', 'post_assessment'] and correct == 'correct': + Correct + % elif state in ['done', 'post_assessment'] and correct == 'incorrect': Incorrect % elif state == 'assessing': Submitted for grading From 74222f9ef6f6a5088b862de66932d1584167b6b9 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 4 Jan 2013 18:55:57 -0500 Subject: [PATCH 078/171] Fix silly mistake --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 694b0ae481..45d0db8dac 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -26,7 +26,7 @@ class @CombinedOpenEnded @submit_button = @$('.submit-button') @child_state = @el.data('state') @child_type = @el.data('child-type') - if @child_type="openended" + if @child_type=="openended" @reload_button = @$('.reload-button') @open_ended_child= @$('.open-ended-child') From 80763c9baefd39e3bd9eaa846b3897a82a86ded2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 09:44:22 -0500 Subject: [PATCH 079/171] Remove some unneeded imports --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 1 - common/lib/xmodule/xmodule/open_ended_module.py | 1 - 2 files changed, 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4fcff80214..0c9ddd6bb9 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -43,7 +43,6 @@ class CombinedOpenEndedModule(XModule): DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] - #, resource_string(__name__, 'js/src/openended/display.coffee') js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} js_module_name = "CombinedOpenEnded" diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 5bd17c220a..e3e23c46d6 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -25,7 +25,6 @@ from .editing_module import EditingDescriptor from .html_checker import check_html from progress import Progress from .stringify import stringify_children -from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location from capa.util import * From 4b7195f21ee6b3561fe1a6dc0a85f521b23d97c9 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:01:29 -0500 Subject: [PATCH 080/171] Fix location problem --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 1 + common/lib/xmodule/xmodule/open_ended_module.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 0c9ddd6bb9..4fddb29bf1 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -77,6 +77,7 @@ class CombinedOpenEndedModule(XModule): # element. # Scores are on scale from 0 to max_score system.set('location', location) + log.debug(system.location) self.current_task_number = instance_state.get('current_task_number', 0) self.task_states= instance_state.get('task_states', []) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e3e23c46d6..e0911b6489 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -179,9 +179,9 @@ class OpenEndedModule(): self.initial_display = find_with_default(oeparam, 'initial_display', '') self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') - + parsed_grader_payload.update({ - 'location' : system.location, + 'location' : system.location.url(), 'course_id' : system.course_id, 'prompt' : prompt_string, 'rubric' : rubric_string, From d8f6e1fe341b980143535411d2ac476bd45bb6c1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:13:59 -0500 Subject: [PATCH 081/171] Factor out init modules, add in status display --- .../xmodule/combined_open_ended_module.py | 3 + .../lib/xmodule/xmodule/open_ended_module.py | 87 +----------- common/lib/xmodule/xmodule/openendedchild.py | 130 ++++++++++++++++++ .../xmodule/xmodule/self_assessment_module.py | 83 +---------- lms/templates/combined_open_ended.html | 3 + lms/templates/combined_open_ended_status.html | 3 + 6 files changed, 146 insertions(+), 163 deletions(-) create mode 100644 common/lib/xmodule/xmodule/openendedchild.py create mode 100644 lms/templates/combined_open_ended_status.html diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4fddb29bf1..ecdc67a120 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -283,6 +283,9 @@ class CombinedOpenEndedModule(XModule): return json.dumps(state) + def get_status(self): + pass + class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding self assessment questions to courses diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e0911b6489..174fa5e326 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -28,6 +28,7 @@ from .stringify import stringify_children from .xml_module import XmlDescriptor from xmodule.modulestore import Location from capa.util import * +import openendedchild from datetime import datetime @@ -42,89 +43,9 @@ MAX_ATTEMPTS = 1 # Overriden by max_score specified in xml. MAX_SCORE = 1 -class OpenEndedModule(): - """ - States: - - initial (prompt, textbox shown) - | - assessing (read-only textbox, rubric + assessment input shown) - | - request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) - | - done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows - a reset button that goes back to initial state. Saves previous - submissions too.) - """ - - DEFAULT_QUEUE = 'open-ended' - DEFAULT_MESSAGE_QUEUE = 'open-ended-message' - max_inputfields = 1 - - STATE_VERSION = 1 - - # states - INITIAL = 'initial' - ASSESSING = 'assessing' - POST_ASSESSMENT = 'post_assessment' - DONE = 'done' - - def __init__(self, system, location, definition, descriptor, - instance_state=None, shared_state=None, **kwargs): - """ - Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, - and two optional attributes: - attempts, which should be an integer that defaults to 1. - If it's > 1, the student will be able to re-submit after they see - the rubric. - max_score, which should be an integer that defaults to 1. - It defines the maximum number of points a student can get. Assumed to be integer scale - from 0 to max_score, with an interval of 1. - - Note: all the submissions are stored. - - Sample file: - - - - Insert prompt text here. (arbitrary html) - - - Insert grading rubric here. (arbitrary html) - - - Please enter a hint below: (arbitrary html) - - - Thanks for submitting! (arbitrary html) - - - """ - - # Load instance state - if instance_state is not None: - log.debug(instance_state) - instance_state = json.loads(instance_state) - else: - instance_state = {} - - # History is a list of tuples of (answer, score, hint), where hint may be - # None for any element, and score and hint can be None for the last (current) - # element. - # Scores are on scale from 0 to max_score - self.history = instance_state.get('history', []) - - self.state = instance_state.get('state', 'initial') - - self.created = instance_state.get('created', "False") - - self.attempts = instance_state.get('attempts', 0) - self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) - - # Used for progress / grading. Currently get credit just for - # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = int(instance_state.get('max_score', MAX_SCORE)) +class OpenEndedModule(openendedchild.OpenEndedChild): + def setup_response(self, system, location, definition, descriptor): oeparam = definition['oeparam'] prompt = definition['prompt'] rubric = definition['rubric'] @@ -179,7 +100,7 @@ class OpenEndedModule(): self.initial_display = find_with_default(oeparam, 'initial_display', '') self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') - + parsed_grader_payload.update({ 'location' : system.location.url(), 'course_id' : system.course_id, diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py new file mode 100644 index 0000000000..b8162800a9 --- /dev/null +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -0,0 +1,130 @@ +""" +A Self Assessment module that allows students to write open-ended responses, +submit, then see a rubric and rate themselves. Persists student supplied +hints, answers, and assessment judgment (currently only correct/incorrect). +Parses xml definition file--see below for exact format. +""" + +import copy +from fs.errors import ResourceNotFoundError +import itertools +import json +import logging +from lxml import etree +from lxml.html import rewrite_links +from path import path +import os +import sys +import hashlib +import capa.xqueue_interface as xqueue_interface + +from pkg_resources import resource_string + +from .capa_module import only_one, ComplexEncoder +from .editing_module import EditingDescriptor +from .html_checker import check_html +from progress import Progress +from .stringify import stringify_children +from .xml_module import XmlDescriptor +from xmodule.modulestore import Location +from capa.util import * + +from datetime import datetime + +log = logging.getLogger("mitx.courseware") + +# Set the default number of max attempts. Should be 1 for production +# Set higher for debugging/testing +# attempts specified in xml definition overrides this. +MAX_ATTEMPTS = 1 + +# Set maximum available number of points. +# Overriden by max_score specified in xml. +MAX_SCORE = 1 + +class OpenEndedChild(): + """ + States: + + initial (prompt, textbox shown) + | + assessing (read-only textbox, rubric + assessment input shown) + | + request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) + | + done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows + a reset button that goes back to initial state. Saves previous + submissions too.) + """ + + DEFAULT_QUEUE = 'open-ended' + DEFAULT_MESSAGE_QUEUE = 'open-ended-message' + max_inputfields = 1 + + STATE_VERSION = 1 + + # states + INITIAL = 'initial' + ASSESSING = 'assessing' + POST_ASSESSMENT = 'post_assessment' + DONE = 'done' + + def __init__(self, system, location, definition, descriptor, + instance_state=None, shared_state=None, **kwargs): + """ + Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, + and two optional attributes: + attempts, which should be an integer that defaults to 1. + If it's > 1, the student will be able to re-submit after they see + the rubric. + max_score, which should be an integer that defaults to 1. + It defines the maximum number of points a student can get. Assumed to be integer scale + from 0 to max_score, with an interval of 1. + + Note: all the submissions are stored. + + Sample file: + + + + Insert prompt text here. (arbitrary html) + + + Insert grading rubric here. (arbitrary html) + + + Please enter a hint below: (arbitrary html) + + + Thanks for submitting! (arbitrary html) + + + """ + + # Load instance state + if instance_state is not None: + instance_state = json.loads(instance_state) + else: + instance_state = {} + + # History is a list of tuples of (answer, score, hint), where hint may be + # None for any element, and score and hint can be None for the last (current) + # element. + # Scores are on scale from 0 to max_score + self.history = instance_state.get('history', []) + + self.state = instance_state.get('state', 'initial') + + self.created = instance_state.get('created', "False") + + self.attempts = instance_state.get('attempts', 0) + self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) + + # Used for progress / grading. Currently get credit just for + # completion (doesn't matter if you self-assessed correct/incorrect). + self._max_score = int(instance_state.get('max_score', MAX_SCORE)) + + self.setup_response(system, location, definition, descriptor) + + def setup_response(self, system, location, definition, descriptor): + pass \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 64edb2bb8a..6c064bebf8 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -26,6 +26,7 @@ from .stringify import stringify_children from .x_module import XModule from .xml_module import XmlDescriptor from xmodule.modulestore import Location +import openendedchild log = logging.getLogger("mitx.courseware") @@ -38,92 +39,14 @@ MAX_ATTEMPTS = 1 # Overriden by max_score specified in xml. MAX_SCORE = 1 -class SelfAssessmentModule(): - """ - States: - - initial (prompt, textbox shown) - | - assessing (read-only textbox, rubric + assessment input shown) - | - request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) - | - done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows - a reset button that goes back to initial state. Saves previous - submissions too.) - """ - - STATE_VERSION = 1 - - # states - INITIAL = 'initial' - ASSESSING = 'assessing' - REQUEST_HINT = 'post_assessment' - DONE = 'done' - - def __init__(self, system, location, definition, descriptor, - instance_state=None, shared_state=None, **kwargs): - """ - Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, - and two optional attributes: - attempts, which should be an integer that defaults to 1. - If it's > 1, the student will be able to re-submit after they see - the rubric. - max_score, which should be an integer that defaults to 1. - It defines the maximum number of points a student can get. Assumed to be integer scale - from 0 to max_score, with an interval of 1. - - Note: all the submissions are stored. - - Sample file: - - - - Insert prompt text here. (arbitrary html) - - - Insert grading rubric here. (arbitrary html) - - - Please enter a hint below: (arbitrary html) - - - Thanks for submitting! (arbitrary html) - - - """ - - # Load instance state - if instance_state is not None: - instance_state = json.loads(instance_state) - else: - instance_state = {} - - instance_state = self.convert_state_to_current_format(instance_state) - - # History is a list of tuples of (answer, score, hint), where hint may be - # 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.created = instance_state.get('created', "False") - - self.state = instance_state.get('state', 'initial') - - self.attempts = instance_state.get('attempts', 0) - self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) - - # Used for progress / grading. Currently get credit just for - # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = int(instance_state.get('max_score', MAX_SCORE)) +class SelfAssessmentModule(openendedchild.OpenEndedChild): + def setup_response(self, system, location, definition, descriptor): self.rubric = definition['rubric'] self.prompt = definition['prompt'] self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] - def latest_answer(self): """None if not available""" if not self.history: diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 5b6823e809..a050dad906 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -6,5 +6,8 @@ + +
${status | n}
+
diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html new file mode 100644 index 0000000000..770870b077 --- /dev/null +++ b/lms/templates/combined_open_ended_status.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file From 5333e5e1fc8195d794f0f6f50fed91c44b6a6eca Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:26:27 -0500 Subject: [PATCH 082/171] Refactoring to open ended child --- .../lib/xmodule/xmodule/open_ended_module.py | 80 ++--------------- common/lib/xmodule/xmodule/openendedchild.py | 82 +++++++++++++++++- .../xmodule/xmodule/self_assessment_module.py | 85 +------------------ 3 files changed, 92 insertions(+), 155 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 174fa5e326..b19ed4aa27 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -69,7 +69,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if self.created=="True" and self.state == self.ASSESSING: self.created="False" - self.get_score(self.latest_answer(), system) + self.send_to_grader(self.latest_answer(), system) self.created="False" def _parse(self, oeparam, prompt, rubric, system): @@ -180,7 +180,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return {'success' : success, 'msg' : "Successfully submitted your feedback."} - def get_score(self, submission, system): + def send_to_grader(self, submission, system): # Prepare xqueue request #------------------------------------------------------------ @@ -228,7 +228,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' self.record_latest_score(score_msg['score']) - self.record_latest_feedback(score_msg['feedback']) + self.record_latest_post_assessment(score_msg['feedback']) self.state=self.POST_ASSESSMENT return True @@ -461,7 +461,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # add new history element with answer and empty score and hint. self.new_history_entry(get['student_answer']) - self.get_score(get['student_answer'], system) + self.send_to_grader(get['student_answer'], system) self.change_state(self.ASSESSING) return {'success': True,} @@ -496,66 +496,16 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if self.state == self.DONE: self.attempts += 1 - def get_instance_state(self): - """ - Get the current score and state - """ - - state = { - 'version': self.STATE_VERSION, - 'history': self.history, - 'state': self.state, - 'max_score': self._max_score, - 'attempts': self.attempts, - 'created' : "False", - } - return json.dumps(state) - - def latest_answer(self): - """None if not available""" - if not self.history: - return "" - return self.history[-1].get('answer', "") - - def latest_score(self): - """None if not available""" - if not self.history: - return "" - return self.history[-1].get('score', "") - - def latest_feedback(self): - """None if not available""" - if not self.history: - return "" - return self.history[-1].get('feedback', "") - - def new_history_entry(self, answer): - self.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 - - def record_latest_feedback(self, feedback): - """Assumes that state is right, so we're adding a score to the latest - history element""" - self.history[-1]['feedback'] = feedback - - def _allow_reset(self): - """Can the module be reset?""" - return self.state == self.DONE and self.attempts < self.max_attempts - def get_html(self, system): #set context variables and render template if self.state != self.INITIAL: latest = self.latest_answer() previous_answer = latest if latest is not None else self.initial_display - feedback = self.latest_feedback() + post_assessment = self.latest_post_assessment() score= self.latest_score() correct = 'correct' if self.is_submission_correct(score) else 'incorrect' else: - feedback="" + post_assessment="" correct="" previous_answer = self.initial_display @@ -567,7 +517,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'rows' : 30, 'cols' : 80, 'id' : 'open_ended', - 'msg' : feedback, + 'msg' : post_assessment, 'child_type' : 'openended', 'correct' : correct, } @@ -575,27 +525,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): html = system.render_template('open_ended.html', context) return html - def max_score(self): - """ - Return max_score - """ - return self._max_score - - def get_score_value(self): - """ - Returns the last score in the list - """ - score = self.latest_score() - return {'score': score if score is not None else 0, - 'total': self._max_score} - def get_progress(self): ''' For now, just return last score / max_score ''' if self._max_score > 0: try: - return Progress(self.get_score_value()['score'], self._max_score) + return Progress(self.get_score()['score'], self._max_score) except Exception as err: log.exception("Got bad progress") return None diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index b8162800a9..e8800d27dd 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -127,4 +127,84 @@ class OpenEndedChild(): self.setup_response(system, location, definition, descriptor) def setup_response(self, system, location, definition, descriptor): - pass \ No newline at end of file + pass + + def latest_answer(self): + """None if not available""" + if not self.history: + return "" + return self.history[-1].get('answer', "") + + def latest_score(self): + """None if not available""" + if not self.history: + return None + return self.history[-1].get('score') + + def latest_post_assessment(self): + """None if not available""" + if not self.history: + return "" + return self.history[-1].get('post_assessment', "") + + def new_history_entry(self, answer): + self.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 + + 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 + + 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: + return + + self.state = new_state + + if self.state == self.DONE: + self.attempts += 1 + + def get_instance_state(self): + """ + Get the current score and state + """ + + state = { + 'version': self.STATE_VERSION, + 'history': self.history, + 'state': self.state, + 'max_score': self._max_score, + 'attempts': self.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 + + def max_score(self): + """ + Return max_score + """ + return self._max_score + + def get_score(self): + """ + Returns the last score in the list + """ + score = self.latest_score() + return {'score': score if score is not None else 0, + 'total': self._max_score} + + + diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 6c064bebf8..864bfee7f8 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -47,51 +47,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] - def latest_answer(self): - """None if not available""" - if not self.history: - return None - return self.history[-1].get('answer') - - def latest_score(self): - """None if not available""" - if not self.history: - return None - return self.history[-1].get('score') - - def latest_hint(self): - """None if not available""" - if not self.history: - return None - return self.history[-1].get('hint') - - def new_history_entry(self, answer): - self.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 - - def record_latest_hint(self, hint): - """Assumes that state is right, so we're adding a score to the latest - history element""" - self.history[-1]['hint'] = hint - - - 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: - return - - self.state = new_state - - if self.state == self.DONE: - self.attempts += 1 - @staticmethod def convert_state_to_current_format(old_state): """ @@ -138,11 +93,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): student_answers, scores, hints)] return new_state - - def _allow_reset(self): - """Can the module be reset?""" - return self.state == self.DONE and self.attempts < self.max_attempts - def get_html(self, system): #set context variables and render template if self.state != self.INITIAL: @@ -166,20 +116,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): html = system.render_template('self_assessment_prompt.html', context) return html - def max_score(self): - """ - Return max_score - """ - return self._max_score - - def get_score(self): - """ - Returns the last score in the list - """ - score = self.latest_score() - return {'score': score if score is not None else 0, - 'total': self._max_score} - def get_progress(self): ''' For now, just return last score / max_score @@ -207,7 +143,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): handlers = { 'save_answer': self.save_answer, 'save_assessment': self.save_assessment, - 'save_post_assessment': self.save_hint, + 'save_post_assessment': self.save_post_assessment, } if dispatch not in handlers: @@ -261,7 +197,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if self.state == self.DONE: # display the previous hint - latest = self.latest_hint() + latest = self.latest_post_assessment() hint = latest if latest is not None else '' else: hint = '' @@ -376,7 +312,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): # the same number of hints and answers. return self.out_of_sync_error(get) - self.record_latest_hint(get['hint']) + self.record_latest_post_assessment(get['hint']) self.change_state(self.DONE) return {'success': True, @@ -403,21 +339,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): return {'success': True} - def get_instance_state(self): - """ - Get the current score and state - """ - - state = { - 'version': self.STATE_VERSION, - 'history': self.history, - 'state': self.state, - 'max_score': self._max_score, - 'attempts': self.attempts, - 'created' : "False", - } - return json.dumps(state) - class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ From 24689024fd28c35130d7d4ff22a416155e4c0a57 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:32:59 -0500 Subject: [PATCH 083/171] Refactor open ended module --- .../lib/xmodule/xmodule/open_ended_module.py | 54 ------------------- common/lib/xmodule/xmodule/openendedchild.py | 38 +++++++++++++ .../xmodule/xmodule/self_assessment_module.py | 41 -------------- 3 files changed, 38 insertions(+), 95 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index b19ed4aa27..de24d30502 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -411,7 +411,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): } ''' handlers = { - 'problem_get': self.get_problem, 'save_answer': self.save_answer, 'score_update': self.update_score, 'save_post_assessment' : self.message_post, @@ -429,23 +428,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): }) return json.dumps(d, cls=ComplexEncoder) - def get_problem(self, get, system): - return self.get_html(system) - - def reset_problem(self, get, system): - self.change_state(self.INITIAL) - return {'success': True} - - def out_of_sync_error(self, get, msg=''): - """ - return dict out-of-sync error message, and also log. - """ - log.warning("Assessment module state out sync. state: %r, get: %r. %s", - self.state, get, msg) - return {'success': False, - 'error': 'The problem state got out-of-sync'} - - def save_answer(self, get, system): if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and @@ -483,19 +465,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return dict() # No AJAX return is needed - 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: - return - - self.state = new_state - - if self.state == self.DONE: - self.attempts += 1 - def get_html(self, system): #set context variables and render template if self.state != self.INITIAL: @@ -525,29 +494,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): html = system.render_template('open_ended.html', context) return html - def get_progress(self): - ''' - For now, just return last score / max_score - ''' - if self._max_score > 0: - try: - return Progress(self.get_score()['score'], self._max_score) - except Exception as err: - log.exception("Got bad progress") - return None - return None - - def reset(self, system): - """ - If resetting is allowed, reset the state. - - Returns {'success': bool, 'error': msg} - (error only present if not success) - """ - self.change_state(self.INITIAL) - return {'success': True} - - class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding self assessment questions to courses diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index e8800d27dd..e152123c37 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -206,5 +206,43 @@ class OpenEndedChild(): return {'score': score if score is not None else 0, 'total': self._max_score} + def reset(self, system): + """ + If resetting is allowed, reset the state. + + Returns {'success': bool, 'error': msg} + (error only present if not success) + """ + self.change_state(self.INITIAL) + return {'success': True} + + def get_progress(self): + ''' + For now, just return last score / max_score + ''' + if self._max_score > 0: + try: + return Progress(self.get_score()['score'], self._max_score) + except Exception as err: + log.exception("Got bad progress") + return None + return None + + def out_of_sync_error(self, get, msg=''): + """ + return dict out-of-sync error message, and also log. + """ + log.warning("Assessment module state out sync. state: %r, get: %r. %s", + self.state, get, msg) + return {'success': False, + 'error': 'The problem state got out-of-sync'} + + def get_html(self): + pass + + def handle_ajax(self): + pass + + diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 864bfee7f8..728a2fd0df 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -116,18 +116,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): html = system.render_template('self_assessment_prompt.html', context) return html - def get_progress(self): - ''' - For now, just return last score / max_score - ''' - if self._max_score > 0: - try: - return Progress(self.get_score()['score'], self._max_score) - except Exception as err: - log.exception("Got bad progress") - return None - return None - def handle_ajax(self, dispatch, get, system): """ @@ -158,15 +146,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): }) return json.dumps(d, cls=ComplexEncoder) - def out_of_sync_error(self, get, msg=''): - """ - return dict out-of-sync error message, and also log. - """ - log.warning("Assessment module state out sync. state: %r, get: %r. %s", - self.state, get, msg) - return {'success': False, - 'error': 'The problem state got out-of-sync'} - def get_rubric_html(self,system): """ Return the appropriate version of the rubric, based on the state. @@ -296,7 +275,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): d['state'] = self.state return d - def save_hint(self, get, system): ''' Save the hint. @@ -320,25 +298,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): 'allow_reset': self._allow_reset()} - def reset(self, system): - """ - If resetting is allowed, reset the state. - - Returns {'success': bool, 'error': msg} - (error only present if not success) - """ - #if self.state != self.DONE: - # return self.out_of_sync_error(get) - - #if self.attempts > self.max_attempts: - # return { - # 'success': False, - # 'error': 'Too many attempts.' - # } - self.change_state(self.INITIAL) - return {'success': True} - - class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ From db7bff293e0c97c46321f4c5d17260d78fec1f11 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:34:44 -0500 Subject: [PATCH 084/171] Add in placeholder status --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index ecdc67a120..75613f7ed1 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -165,6 +165,7 @@ class CombinedOpenEndedModule(XModule): 'state' : self.state, 'task_count' : len(self.task_xml), 'task_number' : self.current_task_number+1, + 'status' : "temporary status." } html = self.system.render_template('combined_open_ended.html', context) From 74d95cde4db12305a1926a2b34dae114f31dca6f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:36:13 -0500 Subject: [PATCH 085/171] Fix naming bug --- common/lib/xmodule/xmodule/self_assessment_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 728a2fd0df..cd8ef72c6e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -131,7 +131,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): handlers = { 'save_answer': self.save_answer, 'save_assessment': self.save_assessment, - 'save_post_assessment': self.save_post_assessment, + 'save_post_assessment': self.save_hint, } if dispatch not in handlers: From 1c6f271bbb73a2e24c396cc542406989f59ba788 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:38:10 -0500 Subject: [PATCH 086/171] Fix naming bug --- common/lib/xmodule/xmodule/self_assessment_module.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index cd8ef72c6e..de0126c3e0 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -160,7 +160,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if self.state == self.ASSESSING: context['read_only'] = False - elif self.state in (self.REQUEST_HINT, self.DONE): + elif self.state in (self.POST_ASSESSMENT, self.DONE): context['read_only'] = True else: raise ValueError("Illegal state '%r'" % self.state) @@ -184,7 +184,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): context = {'hint_prompt': self.hint_prompt, 'hint': hint} - if self.state == self.REQUEST_HINT: + if self.state == self.POST_ASSESSMENT: context['read_only'] = False elif self.state == self.DONE: context['read_only'] = True @@ -269,7 +269,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): d['message_html'] = self.get_message_html() d['allow_reset'] = self._allow_reset() else: - self.change_state(self.REQUEST_HINT) + self.change_state(self.POST_ASSESSMENT) d['hint_html'] = self.get_hint_html(system) d['state'] = self.state @@ -285,7 +285,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.REQUEST_HINT: + if self.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 a06680a23b27e58bc8e402170f1ab0306776758c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:48:05 -0500 Subject: [PATCH 087/171] Move status, remove self assessment format conversion --- .../xmodule/xmodule/self_assessment_module.py | 46 ------------------- lms/templates/combined_open_ended.html | 4 +- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index de0126c3e0..c03620e57f 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -47,52 +47,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] - @staticmethod - def convert_state_to_current_format(old_state): - """ - This module used to use a problematic state representation. This method - converts that into the new format. - - Args: - old_state: dict of state, as passed in. May be old. - - Returns: - new_state: dict of new state - """ - if old_state.get('version', 0) == SelfAssessmentModule.STATE_VERSION: - # already current - return old_state - - # for now, there's only one older format. - - new_state = {'version': SelfAssessmentModule.STATE_VERSION} - - def copy_if_present(key): - if key in old_state: - new_state[key] = old_state[key] - - for to_copy in ['attempts', 'state']: - copy_if_present(to_copy) - - # The answers, scores, and hints need to be kept together to avoid them - # getting out of sync. - - # NOTE: Since there's only one problem with a few hundred submissions - # in production so far, not trying to be smart about matching up hints - # and submissions in cases where they got out of sync. - - student_answers = old_state.get('student_answers', []) - scores = old_state.get('scores', []) - hints = old_state.get('hints', []) - - new_state['history'] = [ - {'answer': answer, - 'score': score, - 'hint': hint} - for answer, score, hint in itertools.izip_longest( - student_answers, scores, hints)] - return new_state - def get_html(self, system): #set context variables and render template if self.state != self.INITIAL: diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index a050dad906..84bded0bec 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,5 +1,7 @@
+
${status | n}
+ % for item in items:
${item['content'] | n}
% endfor @@ -7,7 +9,5 @@ -
${status | n}
-
From 0e2355c279b2fb450ec7f14284c926f460d83728 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 13:58:09 -0500 Subject: [PATCH 088/171] Work on templating for status display --- .../xmodule/combined_open_ended_module.py | 18 ++++++++++++++---- common/lib/xmodule/xmodule/openendedchild.py | 3 +++ .../xmodule/xmodule/self_assessment_module.py | 1 - 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 75613f7ed1..7497e5a8b5 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -98,7 +98,9 @@ class CombinedOpenEndedModule(XModule): return tag def overwrite_state(self, current_task_state): - last_response, last_score=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'] + loaded_task_state=json.loads(current_task_state) if loaded_task_state['state']== self.INITIAL: loaded_task_state['state']=self.ASSESSING @@ -139,7 +141,8 @@ class CombinedOpenEndedModule(XModule): self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: - last_response, last_score=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'] current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) @@ -191,8 +194,10 @@ class CombinedOpenEndedModule(XModule): task=children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) last_response=task.latest_answer() last_score = task.latest_score() + last_post_response = task.latest_post_response() + last_response_dict={'response' : last_response, 'score' : last_score, 'post_response' : post_response, 'type' : task_type} - return last_response, last_score + return last_response_dict def update_task_states(self): changed=False @@ -285,7 +290,12 @@ class CombinedOpenEndedModule(XModule): return json.dumps(state) def get_status(self): - pass + status=[] + for i in xrange(0,self.current_task_number): + task_data = self.get_last_response(i) + status.append(task_data) + context = {'status_list' : status} + status_html = self.system.render_template("combined_open_ended_status.html", context) class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index e152123c37..ebc6e9a9a6 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -243,6 +243,9 @@ class OpenEndedChild(): def handle_ajax(self): pass + def type(self): + pass + diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index c03620e57f..e2651a080e 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -296,7 +296,6 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): 'hintprompt': parse('hintprompt'), } - def definition_to_xml(self, resource_fs): '''Return an xml element representing this definition.''' elt = etree.Element('selfassessment') From c6c77c0cde3efc71145d4d9cc3c98a27740238b2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 14:05:50 -0500 Subject: [PATCH 089/171] Simple status display --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 11 +++++++---- common/lib/xmodule/xmodule/openendedchild.py | 4 ---- lms/templates/combined_open_ended_status.html | 9 ++++++++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 7497e5a8b5..02d6cab373 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -168,7 +168,7 @@ class CombinedOpenEndedModule(XModule): 'state' : self.state, 'task_count' : len(self.task_xml), 'task_number' : self.current_task_number+1, - 'status' : "temporary status." + 'status' : self.get_status(), } html = self.system.render_template('combined_open_ended.html', context) @@ -194,8 +194,9 @@ class CombinedOpenEndedModule(XModule): task=children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) last_response=task.latest_answer() last_score = task.latest_score() - last_post_response = task.latest_post_response() - last_response_dict={'response' : last_response, 'score' : last_score, 'post_response' : post_response, 'type' : task_type} + last_post_assessment = task.latest_post_assessment() + max_score = task.max_score() + last_response_dict={'response' : last_response, 'score' : last_score, 'post_assessment' : last_post_assessment, 'type' : task_type, 'max_score' : max_score} return last_response_dict @@ -273,7 +274,6 @@ class CombinedOpenEndedModule(XModule): self.setup_next_task() return {'success': True} - def get_instance_state(self): """ Get the current score and state @@ -293,10 +293,13 @@ class CombinedOpenEndedModule(XModule): status=[] for i in xrange(0,self.current_task_number): task_data = self.get_last_response(i) + task_data.update({'task_number' : i+1}) status.append(task_data) context = {'status_list' : status} status_html = self.system.render_template("combined_open_ended_status.html", context) + return status_html + class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding self assessment questions to courses diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index ebc6e9a9a6..63ab5b806a 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -243,9 +243,5 @@ class OpenEndedChild(): def handle_ajax(self): pass - def type(self): - pass - - diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 770870b077..bd4dce27e5 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -1,3 +1,10 @@
- + %for status in status_list: + + Step ${status['task_number']} : ${status['score']} / ${status['max_score']} + %if status['type']=="openended": + ${status['post_assessment']} + %endif + + %endfor
\ No newline at end of file From 5e38433494db5c41bc50f910df84163011a849bd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 15:11:01 -0500 Subject: [PATCH 090/171] Add in static data for max score and max attempts --- .../xmodule/combined_open_ended_module.py | 20 +++++++++++++------ .../lib/xmodule/xmodule/open_ended_module.py | 14 ------------- common/lib/xmodule/xmodule/openendedchild.py | 6 +++--- .../xmodule/xmodule/self_assessment_module.py | 9 --------- lms/templates/combined_open_ended_status.html | 2 +- 5 files changed, 18 insertions(+), 33 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 02d6cab373..c47919095f 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -90,6 +90,11 @@ class CombinedOpenEndedModule(XModule): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) + self.static_data = { + 'max_score' : self._max_score, + 'max_attempts' : self.max_attempts, + } + self.task_xml=definition['task_xml'] self.setup_next_task() @@ -137,7 +142,7 @@ class CombinedOpenEndedModule(XModule): self.current_task_descriptor=children['descriptors'][current_task_type](self.system) self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) if current_task_state is None and self.current_task_number==0: - self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor) + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING elif current_task_state is None and self.current_task_number>0: @@ -145,13 +150,13 @@ class CombinedOpenEndedModule(XModule): last_response = last_response_data['response'] current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) self.state=self.ASSESSING else: if self.current_task_number>0 and not reset: current_task_state=self.overwrite_state(current_task_state) - self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, instance_state=current_task_state) + self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) log.debug(self.current_task.get_instance_state()) log.debug(self.get_instance_state()) @@ -191,12 +196,15 @@ class CombinedOpenEndedModule(XModule): task_descriptor=children['descriptors'][task_type](self.system) task_parsed_xml=task_descriptor.definition_from_xml(etree.fromstring(task_xml),self.system) - task=children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, instance_state=task_state) + task=children['modules'][task_type](self.system, self.location, task_parsed_xml, task_descriptor, self.static_data, instance_state=task_state) last_response=task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment() max_score = task.max_score() - last_response_dict={'response' : last_response, 'score' : last_score, 'post_assessment' : last_post_assessment, 'type' : task_type, 'max_score' : max_score} + state = task.state + last_response_dict={'response' : last_response, 'score' : last_score, + 'post_assessment' : last_post_assessment, + 'type' : task_type, 'max_score' : max_score, 'state' : state} return last_response_dict @@ -291,7 +299,7 @@ class CombinedOpenEndedModule(XModule): def get_status(self): status=[] - for i in xrange(0,self.current_task_number): + for i in xrange(0,self.current_task_number+1): task_data = self.get_last_response(i) task_data.update({'task_number' : i+1}) status.append(task_data) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index de24d30502..c607e3eccf 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -34,15 +34,6 @@ from datetime import datetime log = logging.getLogger("mitx.courseware") -# Set the default number of max attempts. Should be 1 for production -# Set higher for debugging/testing -# attempts specified in xml definition overrides this. -MAX_ATTEMPTS = 1 - -# Set maximum available number of points. -# Overriden by max_score specified in xml. -MAX_SCORE = 1 - class OpenEndedModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): @@ -113,11 +104,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.payload = {'grader_payload': updated_grader_payload} - try: - self.max_score = int(find_with_default(oeparam, 'max_score', 1)) - except ValueError: - self.max_score = 1 - def message_post(self,get, system): """ Handles a student message post (a reaction to the grade they received from an open ended grader type) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 63ab5b806a..2e3a5b8e02 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -69,7 +69,7 @@ class OpenEndedChild(): POST_ASSESSMENT = 'post_assessment' DONE = 'done' - def __init__(self, system, location, definition, descriptor, + def __init__(self, system, location, definition, descriptor, static_data, instance_state=None, shared_state=None, **kwargs): """ Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, @@ -118,11 +118,11 @@ class OpenEndedChild(): self.created = instance_state.get('created', "False") self.attempts = instance_state.get('attempts', 0) - self.max_attempts = int(instance_state.get('attempts', MAX_ATTEMPTS)) + self.max_attempts = static_data['max_attempts'] # Used for progress / grading. Currently get credit just for # completion (doesn't matter if you self-assessed correct/incorrect). - self._max_score = int(instance_state.get('max_score', MAX_SCORE)) + self._max_score = static_data['max_score'] self.setup_response(system, location, definition, descriptor) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index e2651a080e..88c47f92ef 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -30,15 +30,6 @@ import openendedchild log = logging.getLogger("mitx.courseware") -# Set the default number of max attempts. Should be 1 for production -# Set higher for debugging/testing -# attempts specified in xml definition overrides this. -MAX_ATTEMPTS = 1 - -# Set maximum available number of points. -# Overriden by max_score specified in xml. -MAX_SCORE = 1 - class SelfAssessmentModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index bd4dce27e5..e53c8136e3 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -1,7 +1,7 @@
%for status in status_list: - Step ${status['task_number']} : ${status['score']} / ${status['max_score']} + Step ${status['task_number']} (${status['state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended": ${status['post_assessment']} %endif From 1e4c85955a7b6b44a80489f7b7780678c2c5dd89 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 15:12:14 -0500 Subject: [PATCH 091/171] Rename max score --- common/lib/xmodule/xmodule/open_ended_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index c607e3eccf..437346828c 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -196,7 +196,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): contents.update({ 'student_info': json.dumps(student_info), 'student_response': submission, - 'max_score' : self.max_score, + 'max_score' : self.max_score(), }) # Submit request. When successful, 'msg' is the prior length of the queue @@ -333,7 +333,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): feedback_template = system.render_template("open_ended_feedback.html", { 'grader_type': response_items['grader_type'], - 'score': "{0} / {1}".format(response_items['score'], self.max_score), + 'score': "{0} / {1}".format(response_items['score'], self.max_score()), 'feedback': feedback, }) @@ -382,7 +382,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def is_submission_correct(self, score): correct=False if(isinstance(score,(int, long, float, complex))): - score_ratio = int(score) / float(self.max_score) + score_ratio = int(score) / float(self.max_score()) correct = (score_ratio >= 0.66) return correct From 0ec1db81693f86c2787a0cb9547d951af381a96a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 15:38:04 -0500 Subject: [PATCH 092/171] Add in a message area --- .../xmodule/js/src/combinedopenended/display.coffee | 12 ++++-------- lms/templates/combined_open_ended.html | 1 - lms/templates/combined_open_ended_status.html | 4 ++-- lms/templates/open_ended.html | 1 + lms/templates/self_assessment_prompt.html | 2 ++ 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 45d0db8dac..4ad0bde631 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -74,13 +74,9 @@ class @CombinedOpenEnded @hint_area.attr('disabled', true) @submit_button.hide() if @task_number<@task_count - @next_problem_button.show() + @next_problem else - @next_problem_button.hide() - #if @allow_reset @reset_button.show() - #else - # @reset_button.hide() find_assessment_elements: -> @@ -161,8 +157,7 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') - next_problem: (event) => - event.preventDefault() + next_problem: => if @child_state == 'done' $.postWithPrefix "#{@ajax_url}/next_problem", {}, (response) => if response.success @@ -203,7 +198,7 @@ class @CombinedOpenEnded processData: false contentType: false success: (response) => - @gentle_alert response.message + @gentle_alert response.msg @$('section.evaluation').slideToggle() @message_wrapper.html(response.message_html) @child_state = 'done' @@ -216,4 +211,5 @@ class @CombinedOpenEnded if @el.find('.open-ended-alert').length @el.find('.open-ended-alert').remove() alert_elem = "
" + msg + "
" + @el.find('.open-ended-action').after(alert_elem) @el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700) \ No newline at end of file diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 84bded0bec..6b30c3f418 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -8,6 +8,5 @@ -
diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index e53c8136e3..6450f5a43e 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -1,10 +1,10 @@
%for status in status_list: - +
Step ${status['task_number']} (${status['state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended": ${status['post_assessment']} %endif - +
%endfor
\ No newline at end of file diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 423b2dc709..838c847841 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -51,5 +51,6 @@
% endif
+
diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 479e42a126..d8186954a7 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -16,4 +16,6 @@
${initial_message}
+
+
From 146e0919f12f6598d0a1553cb4bd0f56d8a6ec77 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 15:59:13 -0500 Subject: [PATCH 093/171] Work on alerts, transitions between pages --- .../xmodule/xmodule/combined_open_ended_module.py | 2 +- .../xmodule/js/src/combinedopenended/display.coffee | 12 +++++++++--- lms/templates/open_ended.html | 5 +++-- lms/templates/self_assessment_prompt.html | 5 +++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index c47919095f..1761434a09 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -255,7 +255,7 @@ class CombinedOpenEndedModule(XModule): def next_problem(self, get): self.update_task_states() - return {'success' : True} + return {'success' : True, 'html' : self.get_html()} def reset(self, get): """ diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 4ad0bde631..94cbdd5fe4 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -1,6 +1,11 @@ class @CombinedOpenEnded constructor: (element) -> + @element=element + @reinitialize(element) + + reinitialize: (element) -> @el = $(element).find('section.combined-open-ended') + @combined_open_ended=$(element).find('section.combined-open-ended') @id = @el.data('id') @ajax_url = @el.data('ajax-url') @state = @el.data('state') @@ -12,7 +17,6 @@ class @CombinedOpenEnded @reset_button.click @reset @next_problem_button = @$('.next-step-button') @next_problem_button.click @next_problem - @combined_open_ended= @$('.combined-open-ended') # valid states: 'initial', 'assessing', 'post_assessment', 'done' # Where to put the rubric once we load it @@ -74,7 +78,7 @@ class @CombinedOpenEnded @hint_area.attr('disabled', true) @submit_button.hide() if @task_number<@task_count - @next_problem + @next_problem() else @reset_button.show() @@ -166,9 +170,11 @@ class @CombinedOpenEnded @hint_wrapper.html('') @message_wrapper.html('') @child_state = 'initial' + @combined_open_ended.html(response.html) + @reinitialize(@element) @rebind() @next_problem_button.hide() - location.reload() + @gentle_alert "Moved to next step." else @errors_area.html(response.error) else diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 838c847841..2c0600cee0 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -24,6 +24,9 @@ +
+
+ @@ -51,6 +54,4 @@ % endif -
-
diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index d8186954a7..4ef057fa34 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -9,6 +9,9 @@ +
+
+
${initial_rubric}
${initial_hint}
@@ -16,6 +19,4 @@
${initial_message}
-
-
From 8d6a5c45f31c6b833745265e00901f64cbf619a4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 16:37:51 -0500 Subject: [PATCH 094/171] Try to render without module system --- .../xmodule/combined_open_ended_module.py | 16 +++++++++++++--- .../js/src/combinedopenended/display.coffee | 6 ++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 1761434a09..29c5265792 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -22,6 +22,8 @@ from xmodule.modulestore import Location import self_assessment_module import open_ended_module +from mitxmako.shortcuts import render_to_string + log = logging.getLogger("mitx.courseware") # Set the default number of max attempts. Should be 1 for production @@ -162,7 +164,7 @@ class CombinedOpenEndedModule(XModule): log.debug(self.get_instance_state()) return True - def get_html(self): + def get_context(self): task_html=self.get_html_base() #set context variables and render template @@ -176,9 +178,17 @@ class CombinedOpenEndedModule(XModule): 'status' : self.get_status(), } + return context + + def get_html(self): + context=self.get_context() html = self.system.render_template('combined_open_ended.html', context) return html + def get_html_nonsystem(self): + context=self.get_context() + html = render_to_string('combined_open_ended.html', context) + return html def get_html_base(self): self.update_task_states() @@ -255,7 +265,7 @@ class CombinedOpenEndedModule(XModule): def next_problem(self, get): self.update_task_states() - return {'success' : True, 'html' : self.get_html()} + return {'success' : True, 'html' : self.get_html_nonsystem()} def reset(self, get): """ @@ -280,7 +290,7 @@ class CombinedOpenEndedModule(XModule): self.task_states[self.current_task_number]=self.current_task.get_instance_state() self.current_task_number=0 self.setup_next_task() - return {'success': True} + return {'success': True, 'html' : self.get_html_nonsystem()} def get_instance_state(self): """ diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 94cbdd5fe4..a12898e14a 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -4,6 +4,7 @@ class @CombinedOpenEnded @reinitialize(element) reinitialize: (element) -> + @wrapper=$(element).find('section.xmodule_CombinedOpenEndedModule') @el = $(element).find('section.combined-open-ended') @combined_open_ended=$(element).find('section.combined-open-ended') @id = @el.data('id') @@ -153,9 +154,10 @@ class @CombinedOpenEnded @hint_wrapper.html('') @message_wrapper.html('') @child_state = 'initial' + @combined_open_ended.after(response.html).remove() + @reinitialize(@element) @rebind() @reset_button.hide() - location.reload() else @errors_area.html(response.error) else @@ -170,7 +172,7 @@ class @CombinedOpenEnded @hint_wrapper.html('') @message_wrapper.html('') @child_state = 'initial' - @combined_open_ended.html(response.html) + @combined_open_ended.after(response.html).remove() @reinitialize(@element) @rebind() @next_problem_button.hide() From 792e57ac62fb4d5ba789cb269d438b682da6e884 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 16:40:11 -0500 Subject: [PATCH 095/171] Remove check for feedback button --- .../xmodule/js/src/combinedopenended/display.coffee | 7 ------- lms/templates/open_ended.html | 1 - 2 files changed, 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index a12898e14a..b9dd460b5f 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -31,8 +31,6 @@ class @CombinedOpenEnded @submit_button = @$('.submit-button') @child_state = @el.data('state') @child_type = @el.data('child-type') - if @child_type=="openended" - @reload_button = @$('.reload-button') @open_ended_child= @$('.open-ended-child') @@ -52,8 +50,6 @@ class @CombinedOpenEnded @reset_button.hide() @next_problem_button.hide() @hint_area.attr('disabled', false) - if @child_type=="openended" - @reload_button.hide() if @child_state == 'initial' @answer_area.attr("disabled", false) @submit_button.prop('value', 'Submit') @@ -64,10 +60,7 @@ class @CombinedOpenEnded @submit_button.click @save_assessment if @child_type == "openended" @submit_button.hide() - @reload_button.show() else if @child_state == 'post_assessment' - if @child_type=="openended" - @reload_button.hide() @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') if @child_type=="selfassessment" diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 2c0600cee0..93e931fa83 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -29,7 +29,6 @@ -
${msg|n} % if state == 'post_assessment': From b6774ed2e8058069e604a5e4e4a187d547dea541 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 16:44:10 -0500 Subject: [PATCH 096/171] Add back check for feedback button. Can always remove later --- .../xmodule/js/src/combinedopenended/display.coffee | 7 +++++++ lms/templates/open_ended.html | 1 + 2 files changed, 8 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index b9dd460b5f..a12898e14a 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -31,6 +31,8 @@ class @CombinedOpenEnded @submit_button = @$('.submit-button') @child_state = @el.data('state') @child_type = @el.data('child-type') + if @child_type=="openended" + @reload_button = @$('.reload-button') @open_ended_child= @$('.open-ended-child') @@ -50,6 +52,8 @@ class @CombinedOpenEnded @reset_button.hide() @next_problem_button.hide() @hint_area.attr('disabled', false) + if @child_type=="openended" + @reload_button.hide() if @child_state == 'initial' @answer_area.attr("disabled", false) @submit_button.prop('value', 'Submit') @@ -60,7 +64,10 @@ class @CombinedOpenEnded @submit_button.click @save_assessment if @child_type == "openended" @submit_button.hide() + @reload_button.show() else if @child_state == 'post_assessment' + if @child_type=="openended" + @reload_button.hide() @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') if @child_type=="selfassessment" diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 93e931fa83..2c0600cee0 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -29,6 +29,7 @@ +
${msg|n} % if state == 'post_assessment': From a7c64b8e2d16a23d19bac48e95778309b9fe39c0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:06:52 -0500 Subject: [PATCH 097/171] Add collapsible --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 4 +++- lms/templates/open_ended.html | 4 ++-- 2 files changed, 5 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 29c5265792..5f68ab411f 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -45,7 +45,9 @@ class CombinedOpenEndedModule(XModule): DONE = 'done' TASK_TYPES=["self", "ml", "instructor", "peer"] - js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee')]} + js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'), + resource_string(__name__, 'js/src/collapsible.coffee'), + ]} js_module_name = "CombinedOpenEnded" css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]} diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 2c0600cee0..31599ecea5 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -1,12 +1,12 @@
-
${prompt|n}
-
+
+
% if state == 'initial': Unanswered % elif state in ['done', 'post_assessment'] and correct == 'correct': From c7f0ba7bc45940c46c82ca3c275705a093d4e93c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:11:04 -0500 Subject: [PATCH 098/171] Fix some of the styling issues --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 1 + .../xmodule/xmodule/css/combinedopenended/display.scss | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 5f68ab411f..2c2e9b57f5 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -47,6 +47,7 @@ class CombinedOpenEndedModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), + resource_string(__name__, 'js/src/javascript_loader.coffee'), ]} js_module_name = "CombinedOpenEnded" diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 73fa4018d2..c13a975c85 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -63,7 +63,6 @@ section.open-ended-child { } } - div { p { &.answer { margin-top: -2px; @@ -74,7 +73,7 @@ section.open-ended-child { } } - &.unanswered { + div.unanswered { p.status { @include inline-block(); background: url('../images/unanswered-icon.png') center center no-repeat; @@ -83,7 +82,7 @@ section.open-ended-child { } } - &.correct, &.ui-icon-check { + div.correct, div.ui-icon-check { p.status { @include inline-block(); background: url('../images/correct-icon.png') center center no-repeat; @@ -96,7 +95,7 @@ section.open-ended-child { } } - &.processing { + div.processing { p.status { @include inline-block(); background: url('../images/spinner.gif') center center no-repeat; @@ -109,7 +108,7 @@ section.open-ended-child { } } - &.incorrect, &.ui-icon-close { + div.incorrect, div.ui-icon-close { p.status { @include inline-block(); background: url('../images/incorrect-icon.png') center center no-repeat; @@ -287,7 +286,6 @@ section.open-ended-child { .submit-message-container { margin: 10px 0px ; } - } form.option-input { margin: -10px 0 20px; From 3ea59ef3fefc2b185ecdc75f584b3787ae5d85ff Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:15:08 -0500 Subject: [PATCH 099/171] set collapsible elements --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index a12898e14a..cfa2ef8331 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -19,6 +19,7 @@ class @CombinedOpenEnded @next_problem_button = @$('.next-step-button') @next_problem_button.click @next_problem # valid states: 'initial', 'assessing', 'post_assessment', 'done' + Collapsible.setCollapsibles(@el) # Where to put the rubric once we load it @el = $(element).find('section.open-ended-child') From c2a285e0c7a251cf7871a41b3fb9f82d719f5778 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:20:59 -0500 Subject: [PATCH 100/171] store feedback in dictionary and only parse when needed --- .../lib/xmodule/xmodule/open_ended_module.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 437346828c..5eaf3f94b7 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -30,6 +30,8 @@ from xmodule.modulestore import Location from capa.util import * import openendedchild +from mitxmako.shortcuts import render_to_string + from datetime import datetime log = logging.getLogger("mitx.courseware") @@ -209,8 +211,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return True def _update_score(self, score_msg, queuekey, system): - score_msg = self._parse_score_msg(score_msg, system) - if not score_msg['valid']: + new_score_msg = self._parse_score_msg(score_msg) + if not new_score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' self.record_latest_score(score_msg['score']) @@ -317,7 +319,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return u"\n".join([feedback_list_part1,feedback_list_part2]) - def _format_feedback(self, response_items, system): + def _format_feedback(self, response_items): """ Input: Dictionary called feedback. Must contain keys seen below. @@ -331,7 +333,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return system.render_template("open_ended_error.html", {'errors' : feedback}) - feedback_template = system.render_template("open_ended_feedback.html", { + feedback_template = render_to_string("open_ended_feedback.html", { 'grader_type': response_items['grader_type'], 'score': "{0} / {1}".format(response_items['score'], self.max_score()), 'feedback': feedback, @@ -340,7 +342,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return feedback_template - def _parse_score_msg(self, score_msg, system): + def _parse_score_msg(self, score_msg): """ Grader reply is a JSON-dump of the following dict { 'correct': True/False, @@ -373,12 +375,18 @@ class OpenEndedModule(openendedchild.OpenEndedChild): .format(tag)) return fail - feedback = self._format_feedback(score_result, system) + feedback = self._format_feedback(score_result) self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} + def latest_post_assessment(self): + """None if not available""" + if not self.history: + return "" + return self._parse_score_msg(self.history[-1].get('post_assessment', "")) + def is_submission_correct(self, score): correct=False if(isinstance(score,(int, long, float, complex))): From 573ec68abe73deb33504854c5b7969269c2bd051 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:29:10 -0500 Subject: [PATCH 101/171] Store feedback as dictionary to be parsed later --- common/lib/xmodule/xmodule/open_ended_module.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 5eaf3f94b7..351f098c50 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -215,8 +215,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if not new_score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' - self.record_latest_score(score_msg['score']) - self.record_latest_post_assessment(score_msg['feedback']) + self.record_latest_score(new_score_msg['score']) + self.record_latest_post_assessment(score_msg) self.state=self.POST_ASSESSMENT return True @@ -385,7 +385,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): """None if not available""" if not self.history: return "" - return self._parse_score_msg(self.history[-1].get('post_assessment', "")) + feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', "")) + return feedback_dict['feedback'] if feedback_dict['valid'] else '' def is_submission_correct(self, score): correct=False From d9ea0fcda06712c65bb10f01d217f0ee55af6893 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:47:34 -0500 Subject: [PATCH 102/171] display feedback in status box --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 4 +++- common/lib/xmodule/xmodule/open_ended_module.py | 9 +++++++-- common/lib/xmodule/xmodule/openendedchild.py | 7 +++++++ lms/templates/combined_open_ended_status.html | 6 ++++-- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 2c2e9b57f5..b0fb365d73 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -213,11 +213,13 @@ class CombinedOpenEndedModule(XModule): last_response=task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment() + if task_type=="openended": + last_post_assessment = task.latest_post_assessment(short_feedback=True) max_score = task.max_score() state = task.state last_response_dict={'response' : last_response, 'score' : last_score, 'post_assessment' : last_post_assessment, - 'type' : task_type, 'max_score' : max_score, 'state' : state} + 'type' : task_type, 'max_score' : max_score, 'state' : state, 'human_state' : task.HUMAN_NAMES[state]} return last_response_dict diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 351f098c50..c5cc01964e 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -381,12 +381,17 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} - def latest_post_assessment(self): + def latest_post_assessment(self, short_feedback=False): """None if not available""" if not self.history: return "" + feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', "")) - return feedback_dict['feedback'] if feedback_dict['valid'] else '' + if not short_feedback: + return feedback_dict['feedback'] if feedback_dict['valid'] else '' + + short_feedback = self._convert_longform_feedback_to_html(json.loads(self.history[-1].get('post_assessment', ""))) + return short_feedback if feedback_dict['valid'] else '' def is_submission_correct(self, score): correct=False diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 2e3a5b8e02..236bd03c4c 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -69,6 +69,13 @@ class OpenEndedChild(): POST_ASSESSMENT = 'post_assessment' DONE = 'done' + HUMAN_NAMES={ + 'initial' : 'Started', + 'assessing' : 'Being scored', + 'post_assessment' : 'Scoring finished', + 'done' : 'Problem complete', + } + def __init__(self, system, location, definition, descriptor, static_data, instance_state=None, shared_state=None, **kwargs): """ diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 6450f5a43e..5b8ac5fcbe 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -1,9 +1,11 @@
%for status in status_list:
- Step ${status['task_number']} (${status['state']}) : ${status['score']} / ${status['max_score']} + Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended": - ${status['post_assessment']} +
+ ${status['post_assessment']} +
%endif
%endfor From ea6945a3fe1bffd84ef3a5572fcd96f0af52ff04 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:50:30 -0500 Subject: [PATCH 103/171] Collapsible feedback --- lms/templates/combined_open_ended_status.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 5b8ac5fcbe..529d8dcf15 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -4,7 +4,12 @@ Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended":
- ${status['post_assessment']} +
+ Show Feedback +
+
+ ${status['post_assessment']} +
%endif
From d4dd0cd84c930e80931603491d63807cd479fbd3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 17:55:38 -0500 Subject: [PATCH 104/171] Html changes --- common/lib/xmodule/xmodule/js/src/collapsible.coffee | 2 +- lms/templates/combined_open_ended_status.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/collapsible.coffee b/common/lib/xmodule/xmodule/js/src/collapsible.coffee index 18a186e106..e414935784 100644 --- a/common/lib/xmodule/xmodule/js/src/collapsible.coffee +++ b/common/lib/xmodule/xmodule/js/src/collapsible.coffee @@ -22,7 +22,7 @@ class @Collapsible if $(event.target).text() == 'See full output' new_text = 'Hide output' else - new_text = 'See full ouput' + new_text = 'See full output' $(event.target).text(new_text) @toggleHint: (event) => diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 529d8dcf15..41ec403c47 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -5,7 +5,7 @@ %if status['type']=="openended":
- Show Feedback + Show feedback from step ${status['task_number']}
${status['post_assessment']} From 8d80fcfb8694237c11a95fda8631d16ec368229e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:05:19 -0500 Subject: [PATCH 105/171] Add button to skip post assessment --- .../xmodule/js/src/combinedopenended/display.coffee | 4 ++++ common/lib/xmodule/xmodule/open_ended_module.py | 9 +++++++-- lms/templates/open_ended.html | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index cfa2ef8331..ca2b707629 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -34,6 +34,7 @@ class @CombinedOpenEnded @child_type = @el.data('child-type') if @child_type=="openended" @reload_button = @$('.reload-button') + @skip_button = @$('.skip-button') @open_ended_child= @$('.open-ended-child') @@ -52,6 +53,7 @@ class @CombinedOpenEnded @submit_button.show() @reset_button.hide() @next_problem_button.hide() + @skip_button.hide() @hint_area.attr('disabled', false) if @child_type=="openended" @reload_button.hide() @@ -69,6 +71,7 @@ class @CombinedOpenEnded else if @child_state == 'post_assessment' if @child_type=="openended" @reload_button.hide() + @skip_button.show() @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') if @child_type=="selfassessment" @@ -79,6 +82,7 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() + @skip_button.hide() if @task_number<@task_count @next_problem() else diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index c5cc01964e..e1a34d284b 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -106,6 +106,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.payload = {'grader_payload': updated_grader_payload} + def skip_post_assessment(self, get, system): + self.state=self.DONE + return {'success' : True} + def message_post(self,get, system): """ Handles a student message post (a reaction to the grade they received from an open ended grader type) @@ -389,8 +393,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', "")) if not short_feedback: return feedback_dict['feedback'] if feedback_dict['valid'] else '' - - short_feedback = self._convert_longform_feedback_to_html(json.loads(self.history[-1].get('post_assessment', ""))) + if feedback_dict['valid']: + short_feedback = self._convert_longform_feedback_to_html(json.loads(self.history[-1].get('post_assessment', ""))) return short_feedback if feedback_dict['valid'] else '' def is_submission_correct(self, score): @@ -414,6 +418,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'save_answer': self.save_answer, 'score_update': self.update_score, 'save_post_assessment' : self.message_post, + 'skip_post_assessment' : self.skip_post_assessment(), } if dispatch not in handlers: diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 31599ecea5..1235014f82 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -28,7 +28,7 @@
- +
${msg|n} From ea5db36e927819a9705c982ced120c5aa7054488 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:08:48 -0500 Subject: [PATCH 106/171] Skip post assessment button --- .../js/src/combinedopenended/display.coffee | 14 ++++++++++++++ common/lib/xmodule/xmodule/open_ended_module.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index ca2b707629..74a30d05a1 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -35,6 +35,7 @@ class @CombinedOpenEnded if @child_type=="openended" @reload_button = @$('.reload-button') @skip_button = @$('.skip-button') + @skip_button.click @skip_post_assessment() @open_ended_child= @$('.open-ended-child') @@ -148,6 +149,19 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') + skip_post_assessment: (event) => + event.preventDefault() + if @child_state == 'post_assessment' + + $.postWithPrefix "#{@ajax_url}/skip_post_assessment", data, (response) => + if response.success + @child_state = 'done' + @allow_reset = response.allow_reset + @rebind() + else + @errors_area.html(response.error) + else + @errors_area.html('Problem state got out of sync. Try reloading the page.') reset: (event) => event.preventDefault() diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e1a34d284b..b1931f81be 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -418,7 +418,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'save_answer': self.save_answer, 'score_update': self.update_score, 'save_post_assessment' : self.message_post, - 'skip_post_assessment' : self.skip_post_assessment(), + 'skip_post_assessment' : self.skip_post_assessment, } if dispatch not in handlers: From e46ecdb6e2296f18589ffa8d47af5c46e788abe2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:10:00 -0500 Subject: [PATCH 107/171] Move skip button --- lms/templates/open_ended.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 1235014f82..e995d62cb1 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -23,12 +23,13 @@
+
- +
${msg|n} From bd5ca9d2a7fb35cbef1822d40a59c21abc86d396 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:11:02 -0500 Subject: [PATCH 108/171] JS bug fix --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 74a30d05a1..187366d3a6 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -35,7 +35,7 @@ class @CombinedOpenEnded if @child_type=="openended" @reload_button = @$('.reload-button') @skip_button = @$('.skip-button') - @skip_button.click @skip_post_assessment() + @skip_button.click @skip_post_assessment @open_ended_child= @$('.open-ended-child') From f5cd2ff9c557dd9e4bfb4908d970111c275a55bc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:13:03 -0500 Subject: [PATCH 109/171] JS bugfix --- .../lib/xmodule/xmodule/js/src/combinedopenended/display.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 187366d3a6..ff06e6a850 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -153,7 +153,7 @@ class @CombinedOpenEnded event.preventDefault() if @child_state == 'post_assessment' - $.postWithPrefix "#{@ajax_url}/skip_post_assessment", data, (response) => + $.postWithPrefix "#{@ajax_url}/skip_post_assessment", {}, (response) => if response.success @child_state = 'done' @allow_reset = response.allow_reset From 9a4ee350d7e4e9facff77e6370e7b0814f0faabc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:21:02 -0500 Subject: [PATCH 110/171] JS tweaks, move feedback into its own template --- .../js/src/combinedopenended/display.coffee | 9 ++++---- lms/templates/combined_open_ended_status.html | 2 +- lms/templates/open_ended.html | 23 +------------------ lms/templates/open_ended_evaluation.html | 22 ++++++++++++++++++ 4 files changed, 29 insertions(+), 27 deletions(-) create mode 100644 lms/templates/open_ended_evaluation.html diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index ff06e6a850..f6ad7eb22a 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -54,10 +54,10 @@ class @CombinedOpenEnded @submit_button.show() @reset_button.hide() @next_problem_button.hide() - @skip_button.hide() @hint_area.attr('disabled', false) if @child_type=="openended" @reload_button.hide() + @skip_button.hide() if @child_state == 'initial' @answer_area.attr("disabled", false) @submit_button.prop('value', 'Submit') @@ -73,6 +73,7 @@ class @CombinedOpenEnded if @child_type=="openended" @reload_button.hide() @skip_button.show() + @skip_post_assessment() @answer_area.attr("disabled", true) @submit_button.prop('value', 'Submit post-assessment') if @child_type=="selfassessment" @@ -83,7 +84,8 @@ class @CombinedOpenEnded @answer_area.attr("disabled", true) @hint_area.attr('disabled', true) @submit_button.hide() - @skip_button.hide() + if @child_type=="openended" + @skip_button.hide() if @task_number<@task_count @next_problem() else @@ -149,8 +151,7 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') - skip_post_assessment: (event) => - event.preventDefault() + skip_post_assessment: => if @child_state == 'post_assessment' $.postWithPrefix "#{@ajax_url}/skip_post_assessment", {}, (response) => diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 41ec403c47..7bc66d954f 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -3,7 +3,7 @@
Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended": -
+
Show feedback from step ${status['task_number']}
diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index e995d62cb1..9ade05d0e2 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -32,27 +32,6 @@
- ${msg|n} - % if state == 'post_assessment': -
-
- Respond to Feedback -
-
-

How accurate do you find this feedback?

-
-
    -
  • -
  • -
  • -
  • -
  • -
-
-

Additional comments:

- -
-
- % endif + ${msg|n}
diff --git a/lms/templates/open_ended_evaluation.html b/lms/templates/open_ended_evaluation.html new file mode 100644 index 0000000000..018486a1d6 --- /dev/null +++ b/lms/templates/open_ended_evaluation.html @@ -0,0 +1,22 @@ +
+ ${msg|n} +
+
+ Respond to Feedback +
+
+

How accurate do you find this feedback?

+
+
    +
  • +
  • +
  • +
  • +
  • +
+
+

Additional comments:

+ +
+
+
\ No newline at end of file From f287752fcbf548ebe90eb2c42c0d1bf02dd8a1c4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:23:24 -0500 Subject: [PATCH 111/171] CSS tweaks --- common/lib/xmodule/xmodule/css/combinedopenended/display.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index c13a975c85..faa3c79419 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -20,7 +20,7 @@ h2 { color: darken($error-red, 10%); } -section.open-ended-child { +section.open-ended-child, section.combined-open-ended-status { @media print { display: block; width: auto; @@ -396,7 +396,7 @@ section.open-ended-child { } } - div.capa_alert { + div.open-ended-alert { padding: 8px 12px; border: 1px solid #EBE8BF; border-radius: 3px; From 5b0181c925dd327338d256f3ef6d4776e936fbc1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:31:35 -0500 Subject: [PATCH 112/171] Status moved to the right --- .../xmodule/css/combinedopenended/display.scss | 13 +++++++++++++ lms/templates/combined_open_ended.html | 15 +++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index faa3c79419..7e09c85734 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -20,6 +20,19 @@ h2 { color: darken($error-red, 10%); } +section.combined-open-ended { + .status-container + { + float:right; + width:30%; + } + .item-container + { + float:left; + width: 63%; + } +} + section.open-ended-child, section.combined-open-ended-status { @media print { display: block; diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 6b30c3f418..bc68cbe29e 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,12 +1,15 @@
-
${status | n}
+
${status | n}
- % for item in items: -
${item['content'] | n}
- % endfor +
+ % for item in items: +
${item['content'] | n}
+ % endfor - - + + + +
From 97885354162cade069d719490daaf9c61df1052c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:35:33 -0500 Subject: [PATCH 113/171] Get rid of open ended message --- .../xmodule/xmodule/css/combinedopenended/display.scss | 10 ++++++++++ lms/templates/open_ended.html | 1 - 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 7e09c85734..c1112d72b3 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -21,6 +21,7 @@ h2 { } section.combined-open-ended { + margin-right:20px; .status-container { float:right; @@ -31,6 +32,15 @@ section.combined-open-ended { float:left; width: 63%; } + + &:after + { + content:"."; + display:block; + height:0; + visibility: hidden; + clear:both; + } } section.open-ended-child, section.combined-open-ended-status { diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index 9ade05d0e2..b0ef0be51a 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -26,7 +26,6 @@
-
From f70434970eda90389d96634f6c0837da8badcc6e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:39:50 -0500 Subject: [PATCH 114/171] Make everything look a bit nicer --- lms/templates/combined_open_ended.html | 6 +++++- lms/templates/self_assessment_prompt.html | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index bc68cbe29e..5cd5d76e4c 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -1,8 +1,12 @@
-
${status | n}
+
+

Status


+ ${status | n} +
+

Problem


% for item in items:
${item['content'] | n}
% endfor diff --git a/lms/templates/self_assessment_prompt.html b/lms/templates/self_assessment_prompt.html index 4ef057fa34..2ec83ef2a7 100644 --- a/lms/templates/self_assessment_prompt.html +++ b/lms/templates/self_assessment_prompt.html @@ -10,7 +10,6 @@
-
${initial_rubric}
From b83df64f9a0258f1475f3adb6175e7e3ddff8e98 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 18:53:36 -0500 Subject: [PATCH 115/171] Inline response to feedback --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 3 +++ .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 3 ++- common/lib/xmodule/xmodule/open_ended_module.py | 5 +++++ 3 files changed, 10 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 b0fb365d73..4537985d33 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -213,8 +213,11 @@ class CombinedOpenEndedModule(XModule): last_response=task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment() + last_post_feedback="" if task_type=="openended": last_post_assessment = task.latest_post_assessment(short_feedback=True) + last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) + last_post_assessment = last_post_evaluation max_score = task.max_score() state = task.state last_response_dict={'response' : last_response, 'score' : last_score, diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index f6ad7eb22a..100905e0ae 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -30,6 +30,8 @@ class @CombinedOpenEnded @hint_wrapper = @$('.hint-wrapper') @message_wrapper = @$('.message-wrapper') @submit_button = @$('.submit-button') + @submit_evaluation_button = @$('.submit-evaluation-button') + @submit_evaluation_button.click @message_post @child_state = @el.data('state') @child_type = @el.data('child-type') if @child_type=="openended" @@ -134,7 +136,6 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') - save_hint: (event) => event.preventDefault() if @child_state == 'post_assessment' diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index b1931f81be..32245e710b 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -404,6 +404,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): correct = (score_ratio >= 0.66) return correct + def format_feedback_with_evaluation(self,feedback): + context={'msg' : feedback, id : "1", rows : 30, cols : 30} + html= render_to_string('open_ended_evaluation.html', context) + return html + def handle_ajax(self, dispatch, get, system): ''' This is called by courseware.module_render, to handle an AJAX call. From a3c1f7c9f4bb7dd088edc935a673cf8ce61159c6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 19:00:24 -0500 Subject: [PATCH 116/171] Feedback submission moved --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 6 +++--- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- lms/templates/open_ended_evaluation.html | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 100905e0ae..aeaa765b4e 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -20,6 +20,8 @@ class @CombinedOpenEnded @next_problem_button.click @next_problem # valid states: 'initial', 'assessing', 'post_assessment', 'done' Collapsible.setCollapsibles(@el) + @submit_evaluation_button = $('.submit-evaluation-button') + @submit_evaluation_button.click @message_post # Where to put the rubric once we load it @el = $(element).find('section.open-ended-child') @@ -30,8 +32,6 @@ class @CombinedOpenEnded @hint_wrapper = @$('.hint-wrapper') @message_wrapper = @$('.message-wrapper') @submit_button = @$('.submit-button') - @submit_evaluation_button = @$('.submit-evaluation-button') - @submit_evaluation_button.click @message_post @child_state = @el.data('state') @child_type = @el.data('child-type') if @child_type=="openended" @@ -207,7 +207,7 @@ class @CombinedOpenEnded Logger.log 'message_post', @answers fd = new FormData() - feedback = @$('section.evaluation textarea.feedback-on-feedback')[0].value + feedback = $('section.evaluation textarea.feedback-on-feedback')[0].value submission_id = $('div.external-grader-message div.submission_id')[0].innerHTML grader_id = $('div.external-grader-message div.grader_id')[0].innerHTML score = $(".evaluation-scoring input:radio[name='evaluation-score']:checked").val() diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 32245e710b..46b6f7e838 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -405,7 +405,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return correct def format_feedback_with_evaluation(self,feedback): - context={'msg' : feedback, id : "1", rows : 30, cols : 30} + context={'msg' : feedback, 'id' : "1", 'rows' : 30, 'cols' : 30} html= render_to_string('open_ended_evaluation.html', context) return html diff --git a/lms/templates/open_ended_evaluation.html b/lms/templates/open_ended_evaluation.html index 018486a1d6..da3f38b6a9 100644 --- a/lms/templates/open_ended_evaluation.html +++ b/lms/templates/open_ended_evaluation.html @@ -17,6 +17,7 @@

Additional comments:

+
\ No newline at end of file From 25d25c004b2468d3f89a02a87a7083478bb38a73 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 19:16:28 -0500 Subject: [PATCH 117/171] Restyling on feedback response --- .../css/combinedopenended/display.scss | 258 +++++++++--------- 1 file changed, 130 insertions(+), 128 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index c1112d72b3..daed84054e 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -44,6 +44,136 @@ section.combined-open-ended { } section.open-ended-child, section.combined-open-ended-status { + + .evaluation { + p { + margin-bottom: 1px; + } + } + + .feedback-on-feedback { + height: 100px; + width: 150px; + margin-right: 0px; + } + + .evaluation-response { + header { + text-align: right; + a { + font-size: .7em; + } + } + } + .evaluation-scoring { + .scoring-list { + list-style-type: none; + margin-left: 3px; + + li { + &:first-child { + margin-left: 0px; + } + display:block; + margin-left: 0px; + + label { + font-size: .9em; + } + } + } + } + .submit-message-container { + margin: 10px 0px ; + } + + .external-grader-message { + section { + padding-left: 20px; + background-color: #FAFAFA; + color: #2C2C2C; + font-family: monospace; + font-size: 1em; + padding-top: 10px; + header { + font-size: 1.4em; + } + + .shortform { + font-weight: bold; + } + + .longform { + padding: 0px; + margin: 0px; + + .result-errors { + margin: 5px; + padding: 10px 10px 10px 40px; + background: url('../images/incorrect-icon.png') center left no-repeat; + li { + color: #B00; + } + } + + .result-output { + margin: 5px; + padding: 20px 0px 15px 50px; + border-top: 1px solid #DDD; + border-left: 20px solid #FAFAFA; + + h4 { + font-family: monospace; + font-size: 1em; + } + + dl { + margin: 0px; + } + + dt { + margin-top: 20px; + } + + dd { + margin-left: 24pt; + } + } + + .result-correct { + background: url('../images/correct-icon.png') left 20px no-repeat; + .result-actual-output { + color: #090; + } + } + + .result-incorrect { + background: url('../images/incorrect-icon.png') left 20px no-repeat; + .result-actual-output { + color: #B00; + } + } + + .markup-text{ + margin: 5px; + padding: 20px 0px 15px 50px; + border-top: 1px solid #DDD; + border-left: 20px solid #FAFAFA; + + bs { + color: #BB0000; + } + + bg { + color: #BDA046; + } + } + } + } + } +} + +section.open-ended-child { @media print { display: block; width: auto; @@ -265,50 +395,6 @@ section.open-ended-child, section.combined-open-ended-status { } } - .evaluation { - p { - margin-bottom: 4px; - } - } - - - .feedback-on-feedback { - height: 100px; - margin-right: 20px; - } - - .evaluation-response { - header { - text-align: right; - a { - font-size: .85em; - } - } - } - - .evaluation-scoring { - .scoring-list { - list-style-type: none; - margin-left: 3px; - - li { - &:first-child { - margin-left: 0px; - } - display:inline; - margin-left: 50px; - - label { - font-size: .9em; - } - - } - } - - } - .submit-message-container { - margin: 10px 0px ; - } form.option-input { margin: -10px 0 20px; @@ -444,88 +530,4 @@ section.open-ended-child, section.combined-open-ended-status { font-size: 0.9em; } - .external-grader-message { - section { - padding-left: 20px; - background-color: #FAFAFA; - color: #2C2C2C; - font-family: monospace; - font-size: 1em; - padding-top: 10px; - header { - font-size: 1.4em; - } - - .shortform { - font-weight: bold; - } - - .longform { - padding: 0px; - margin: 0px; - - .result-errors { - margin: 5px; - padding: 10px 10px 10px 40px; - background: url('../images/incorrect-icon.png') center left no-repeat; - li { - color: #B00; - } - } - - .result-output { - margin: 5px; - padding: 20px 0px 15px 50px; - border-top: 1px solid #DDD; - border-left: 20px solid #FAFAFA; - - h4 { - font-family: monospace; - font-size: 1em; - } - - dl { - margin: 0px; - } - - dt { - margin-top: 20px; - } - - dd { - margin-left: 24pt; - } - } - - .result-correct { - background: url('../images/correct-icon.png') left 20px no-repeat; - .result-actual-output { - color: #090; - } - } - - .result-incorrect { - background: url('../images/incorrect-icon.png') left 20px no-repeat; - .result-actual-output { - color: #B00; - } - } - - .markup-text{ - margin: 5px; - padding: 20px 0px 15px 50px; - border-top: 1px solid #DDD; - border-left: 20px solid #FAFAFA; - - bs { - color: #BB0000; - } - - bg { - color: #BDA046; - } - } - } - } - } } From 349a43f31c6d1e78953aadb9fc5a6fc7228a6209 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 7 Jan 2013 19:19:51 -0500 Subject: [PATCH 118/171] Decouple message post from state logic --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index aeaa765b4e..2c8cbcda4b 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -220,7 +220,6 @@ class @CombinedOpenEnded else fd.append('score', score) - settings = type: "POST" data: fd @@ -228,11 +227,8 @@ class @CombinedOpenEnded contentType: false success: (response) => @gentle_alert response.msg - @$('section.evaluation').slideToggle() + $('section.evaluation').slideToggle() @message_wrapper.html(response.message_html) - @child_state = 'done' - @allow_reset = response.allow_reset - @rebind() $.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings) From 5273d44788ce59552324a2ed2ded3f6743cacb76 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 10:24:36 -0500 Subject: [PATCH 119/171] Fix css and display current step --- .../css/combinedopenended/display.scss | 18 ++++++++++- lms/templates/combined_open_ended_status.html | 32 +++++++++++-------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index daed84054e..e15a34453a 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -21,7 +21,6 @@ h2 { } section.combined-open-ended { - margin-right:20px; .status-container { float:right; @@ -45,6 +44,23 @@ section.combined-open-ended { section.open-ended-child, section.combined-open-ended-status { + .statusitem { + background-color: #FAFAFA; + color: #2C2C2C; + font-family: monospace; + font-size: 1em; + padding-top: 10px; + } + + .statusitem-current { + background-color: #BEBEBE; + color: #2C2C2C; + font-family: monospace; + font-size: 1em; + padding-top: 10px; + } + + .evaluation { p { margin-bottom: 1px; diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 7bc66d954f..c8a0177edd 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -1,17 +1,23 @@
- %for status in status_list: -
- Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} - %if status['type']=="openended": -
-
- Show feedback from step ${status['task_number']} -
-
- ${status['post_assessment']} -
-
+ %for i in xrange(0,len(status_list)): + <%status=status_list[i]%> + %if i==len(status_list)-1: +
+ %else: +
%endif -
+ + Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} + %if status['type']=="openended": +
+
+ Show feedback from step ${status['task_number']} +
+
+ ${status['post_assessment']} +
+
+ %endif +
%endfor
\ No newline at end of file From 24bfe44049e79cd29f3735ad2faf03cce21c20dc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 10:57:47 -0500 Subject: [PATCH 120/171] Some restyling, automatically check for feedback, remove reload button --- .../css/combinedopenended/display.scss | 8 +++---- .../js/src/combinedopenended/display.coffee | 21 ++++++++++++++----- .../lib/xmodule/xmodule/open_ended_module.py | 7 ++++++- lms/templates/open_ended.html | 1 - 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index e15a34453a..5ac7066c6d 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -24,12 +24,12 @@ section.combined-open-ended { .status-container { float:right; - width:30%; + width:40%; } .item-container { float:left; - width: 63%; + width: 53%; } &:after @@ -68,8 +68,8 @@ section.open-ended-child, section.combined-open-ended-status { } .feedback-on-feedback { - height: 100px; - width: 150px; + height: 150px; + width: 250px; margin-right: 0px; } diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 2c8cbcda4b..e868186bde 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -35,7 +35,6 @@ class @CombinedOpenEnded @child_state = @el.data('state') @child_type = @el.data('child-type') if @child_type=="openended" - @reload_button = @$('.reload-button') @skip_button = @$('.skip-button') @skip_button.click @skip_post_assessment @@ -58,7 +57,6 @@ class @CombinedOpenEnded @next_problem_button.hide() @hint_area.attr('disabled', false) if @child_type=="openended" - @reload_button.hide() @skip_button.hide() if @child_state == 'initial' @answer_area.attr("disabled", false) @@ -70,10 +68,9 @@ class @CombinedOpenEnded @submit_button.click @save_assessment if @child_type == "openended" @submit_button.hide() - @reload_button.show() + @queueing() else if @child_state == 'post_assessment' if @child_type=="openended" - @reload_button.hide() @skip_button.show() @skip_post_assessment() @answer_area.attr("disabled", true) @@ -237,4 +234,18 @@ class @CombinedOpenEnded @el.find('.open-ended-alert').remove() alert_elem = "
" + msg + "
" @el.find('.open-ended-action').after(alert_elem) - @el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700) \ No newline at end of file + @el.find('.open-ended-alert').css(opacity: 0).animate(opacity: 1, 700) + + queueing: => + if @child_state=="assessing" and @child_type=="openended" + if window.queuePollerID # Only one poller 'thread' per Problem + window.clearTimeout(window.queuePollerID) + window.queuePollerID = window.setTimeout(@poll, 10000) + + poll: => + $.postWithPrefix "#{@ajax_url}/check_for_score", (response) => + if response.state == "done" or response.state=="post_assessment" + delete window.queuePollerID + location.reload() + else + window.queuePollerID = window.setTimeout(@poll, 10000) \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 46b6f7e838..ba1c68511f 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -405,7 +405,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return correct def format_feedback_with_evaluation(self,feedback): - context={'msg' : feedback, 'id' : "1", 'rows' : 30, 'cols' : 30} + context={'msg' : feedback, 'id' : "1", 'rows' : 50, 'cols' : 50} html= render_to_string('open_ended_evaluation.html', context) return html @@ -424,6 +424,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'score_update': self.update_score, 'save_post_assessment' : self.message_post, 'skip_post_assessment' : self.skip_post_assessment, + 'check_for_score' : self.check_for_score, } if dispatch not in handlers: @@ -438,6 +439,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): }) return json.dumps(d, cls=ComplexEncoder) + def check_for_score(self, get, system): + state = self.state + return {'state' : state} + def save_answer(self, get, system): if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index b0ef0be51a..c1c7288d9e 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -29,7 +29,6 @@ -
${msg|n}
From 3620fd93d9d13fb0ca66f69dfdf1a7b46aff38d2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 12:13:39 -0500 Subject: [PATCH 121/171] Create a result container and put results into it --- .../xmodule/combined_open_ended_module.py | 9 +++++++++ .../xmodule/css/combinedopenended/display.scss | 7 +++++++ .../js/src/combinedopenended/display.coffee | 17 +++++++++++++++++ lms/templates/combined_open_ended.html | 4 ++++ lms/templates/combined_open_ended_results.html | 3 +++ lms/templates/combined_open_ended_status.html | 9 ++------- lms/templates/open_ended_evaluation.html | 2 +- 7 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 lms/templates/combined_open_ended_results.html diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4537985d33..c6ed8b2ed1 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -248,6 +248,14 @@ class CombinedOpenEndedModule(XModule): pass return return_html + def get_results(self, get): + task_number=get['task_number'] + self.update_task_states() + response_dict=self.get_last_response(task_number) + context = {'results' : response_dict['post_assessment']} + html = render_to_string('combined_open_ended_results.html', context) + return html + def handle_ajax(self, dispatch, get): """ This is called by courseware.module_render, to handle an AJAX call. @@ -262,6 +270,7 @@ class CombinedOpenEndedModule(XModule): handlers = { 'next_problem': self.next_problem, 'reset': self.reset, + 'get_results' : self.get_results } if dispatch not in handlers: diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 5ac7066c6d..d8e36361a6 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -32,6 +32,13 @@ section.combined-open-ended { width: 53%; } + .result-container + { + float:left; + width: 93%; + position:relative; + } + &:after { content:"."; diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index e868186bde..922f4082b0 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -18,11 +18,17 @@ class @CombinedOpenEnded @reset_button.click @reset @next_problem_button = @$('.next-step-button') @next_problem_button.click @next_problem + + @show_results_button=@$('.show-results-button') + @show_results_button.click @show_results + # valid states: 'initial', 'assessing', 'post_assessment', 'done' Collapsible.setCollapsibles(@el) @submit_evaluation_button = $('.submit-evaluation-button') @submit_evaluation_button.click @message_post + @results_container = $('.result-container') + # Where to put the rubric once we load it @el = $(element).find('section.open-ended-child') @errors_area = @$('.error') @@ -49,6 +55,17 @@ class @CombinedOpenEnded $: (selector) -> $(selector, @el) + show_results: (event) => + status_item = $(event.target).parent().parent() + status_number = status_item.data('status-number') + data = {'task_number' : status_number} + $.postWithPrefix "#{@ajax_url}/get_results", data, (response) => + if response.success + @results_container.after(response.html).remove() + @Collapsible.setCollapsibles(@results_container) + else + @errors_area.html(response.error) + rebind: () => # rebind to the appropriate function for the current state @submit_button.unbind('click') diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 5cd5d76e4c..4c0aaa1042 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -15,5 +15,9 @@ + +
+

Results


+
diff --git a/lms/templates/combined_open_ended_results.html b/lms/templates/combined_open_ended_results.html new file mode 100644 index 0000000000..12af086cfe --- /dev/null +++ b/lms/templates/combined_open_ended_results.html @@ -0,0 +1,3 @@ +
+ ${results | n} +
\ No newline at end of file diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index c8a0177edd..6109df2f26 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -9,13 +9,8 @@ Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} %if status['type']=="openended": - diff --git a/lms/templates/open_ended_evaluation.html b/lms/templates/open_ended_evaluation.html index da3f38b6a9..71ce6d5056 100644 --- a/lms/templates/open_ended_evaluation.html +++ b/lms/templates/open_ended_evaluation.html @@ -20,4 +20,4 @@ - \ No newline at end of file +incorrect-icon.png \ No newline at end of file From 943c0ff8207e3cb065e871128c9a8399179ab9df Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:01:35 -0500 Subject: [PATCH 122/171] Display results separately from submission logic --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 6 +++--- .../xmodule/css/combinedopenended/display.scss | 11 +++++++---- .../xmodule/js/src/combinedopenended/display.coffee | 1 + lms/templates/combined_open_ended_results.html | 1 + lms/templates/open_ended.html | 4 ---- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index c6ed8b2ed1..aec330cb49 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -215,7 +215,7 @@ class CombinedOpenEndedModule(XModule): last_post_assessment = task.latest_post_assessment() last_post_feedback="" if task_type=="openended": - last_post_assessment = task.latest_post_assessment(short_feedback=True) + last_post_assessment = task.latest_post_assessment(short_feedback=False) last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) last_post_assessment = last_post_evaluation max_score = task.max_score() @@ -249,12 +249,12 @@ class CombinedOpenEndedModule(XModule): return return_html def get_results(self, get): - task_number=get['task_number'] + task_number=int(get['task_number']) self.update_task_states() response_dict=self.get_last_response(task_number) context = {'results' : response_dict['post_assessment']} html = render_to_string('combined_open_ended_results.html', context) - return html + return {'html' : html, 'success' : True} def handle_ajax(self, dispatch, get): """ diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index d8e36361a6..6adb31aa5d 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -30,6 +30,7 @@ section.combined-open-ended { { float:left; width: 53%; + padding-bottom: 20px; } .result-container @@ -49,7 +50,7 @@ section.combined-open-ended { } } -section.open-ended-child, section.combined-open-ended-status { +section.combined-open-ended-status { .statusitem { background-color: #FAFAFA; @@ -66,9 +67,11 @@ section.open-ended-child, section.combined-open-ended-status { font-size: 1em; padding-top: 10px; } +} +div.result-container { - .evaluation { + .evaluation { p { margin-bottom: 1px; } @@ -84,7 +87,7 @@ section.open-ended-child, section.combined-open-ended-status { header { text-align: right; a { - font-size: .7em; + font-size: .85em; } } } @@ -97,7 +100,7 @@ section.open-ended-child, section.combined-open-ended-status { &:first-child { margin-left: 0px; } - display:block; + display:inline; margin-left: 0px; label { diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 922f4082b0..1dbe3fe9ea 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -62,6 +62,7 @@ class @CombinedOpenEnded $.postWithPrefix "#{@ajax_url}/get_results", data, (response) => if response.success @results_container.after(response.html).remove() + @results_container = $('.result-container') @Collapsible.setCollapsibles(@results_container) else @errors_area.html(response.error) diff --git a/lms/templates/combined_open_ended_results.html b/lms/templates/combined_open_ended_results.html index 12af086cfe..75c5596e4b 100644 --- a/lms/templates/combined_open_ended_results.html +++ b/lms/templates/combined_open_ended_results.html @@ -1,3 +1,4 @@
+

Results


${results | n}
\ No newline at end of file diff --git a/lms/templates/open_ended.html b/lms/templates/open_ended.html index c1c7288d9e..cda3282a45 100644 --- a/lms/templates/open_ended.html +++ b/lms/templates/open_ended.html @@ -28,8 +28,4 @@
- -
- ${msg|n} -
From 7b08114805a3410c4633817db38943469887e244 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:09:48 -0500 Subject: [PATCH 123/171] More restyling, fix buttons --- .../lib/xmodule/xmodule/css/combinedopenended/display.scss | 5 ++--- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 6 ++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 6adb31aa5d..7f52cd9623 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -30,7 +30,7 @@ section.combined-open-ended { { float:left; width: 53%; - padding-bottom: 20px; + padding-bottom: 50px; } .result-container @@ -78,8 +78,7 @@ div.result-container { } .feedback-on-feedback { - height: 150px; - width: 250px; + height: 100px; margin-right: 0px; } diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 1dbe3fe9ea..0e285dce25 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -62,8 +62,10 @@ class @CombinedOpenEnded $.postWithPrefix "#{@ajax_url}/get_results", data, (response) => if response.success @results_container.after(response.html).remove() - @results_container = $('.result-container') - @Collapsible.setCollapsibles(@results_container) + @results_container = $('div.result-container') + @submit_evaluation_button = $('.submit-evaluation-button') + @submit_evaluation_button.click @message_post + Collapsible.setCollapsibles(@results_container) else @errors_area.html(response.error) From af705ca1af28445abb85ca1a5268100654970ac7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:12:21 -0500 Subject: [PATCH 124/171] Remove html --- lms/templates/open_ended_evaluation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/open_ended_evaluation.html b/lms/templates/open_ended_evaluation.html index 71ce6d5056..da3f38b6a9 100644 --- a/lms/templates/open_ended_evaluation.html +++ b/lms/templates/open_ended_evaluation.html @@ -20,4 +20,4 @@ -incorrect-icon.png \ No newline at end of file + \ No newline at end of file From bccafe6c3195403ce321efc4fb20e56d1cfc18e8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:23:54 -0500 Subject: [PATCH 125/171] Add in correctness display --- .../xmodule/combined_open_ended_module.py | 16 ++++++++++++---- common/lib/xmodule/xmodule/open_ended_module.py | 7 ------- common/lib/xmodule/xmodule/openendedchild.py | 11 +++++++++++ lms/templates/combined_open_ended.html | 1 - lms/templates/combined_open_ended_results.html | 2 +- lms/templates/combined_open_ended_status.html | 2 +- 6 files changed, 25 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 aec330cb49..957b978407 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -218,11 +218,19 @@ class CombinedOpenEndedModule(XModule): last_post_assessment = task.latest_post_assessment(short_feedback=False) last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) last_post_assessment = last_post_evaluation + last_correctness = task.is_last_response_correct() max_score = task.max_score() state = task.state - last_response_dict={'response' : last_response, 'score' : last_score, - 'post_assessment' : last_post_assessment, - 'type' : task_type, 'max_score' : max_score, 'state' : state, 'human_state' : task.HUMAN_NAMES[state]} + last_response_dict={ + 'response' : last_response, + 'score' : last_score, + 'post_assessment' : last_post_assessment, + 'type' : task_type, + 'max_score' : max_score, + 'state' : state, + 'human_state' : task.HUMAN_NAMES[state], + 'correct' : last_correctness + } return last_response_dict @@ -252,7 +260,7 @@ class CombinedOpenEndedModule(XModule): task_number=int(get['task_number']) self.update_task_states() response_dict=self.get_last_response(task_number) - context = {'results' : response_dict['post_assessment']} + context = {'results' : response_dict['post_assessment'], 'task_number' : task_number+1} html = render_to_string('combined_open_ended_results.html', context) return {'html' : html, 'success' : True} diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index ba1c68511f..9cdbdd54e2 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -397,13 +397,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): short_feedback = self._convert_longform_feedback_to_html(json.loads(self.history[-1].get('post_assessment', ""))) return short_feedback if feedback_dict['valid'] else '' - def is_submission_correct(self, score): - correct=False - if(isinstance(score,(int, long, float, complex))): - score_ratio = int(score) / float(self.max_score()) - correct = (score_ratio >= 0.66) - return correct - def format_feedback_with_evaluation(self,feedback): context={'msg' : feedback, 'id' : "1", 'rows' : 50, 'cols' : 50} html= render_to_string('open_ended_evaluation.html', context) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 236bd03c4c..73bd8f3957 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -250,5 +250,16 @@ class OpenEndedChild(): def handle_ajax(self): pass + def is_submission_correct(self, score): + correct=False + if(isinstance(score,(int, long, float, complex))): + score_ratio = int(score) / float(self.max_score()) + correct = (score_ratio >= 0.66) + return correct + + def is_last_response_correct(self): + score=self.get_score() + return self.is_submission_correct(score) + diff --git a/lms/templates/combined_open_ended.html b/lms/templates/combined_open_ended.html index 4c0aaa1042..71c22085e3 100644 --- a/lms/templates/combined_open_ended.html +++ b/lms/templates/combined_open_ended.html @@ -17,7 +17,6 @@
-

Results


diff --git a/lms/templates/combined_open_ended_results.html b/lms/templates/combined_open_ended_results.html index 75c5596e4b..db86e95016 100644 --- a/lms/templates/combined_open_ended_results.html +++ b/lms/templates/combined_open_ended_results.html @@ -1,4 +1,4 @@
-

Results


+

Results from Step ${task_number}


${results | n}
\ No newline at end of file diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 6109df2f26..9d51bd67fd 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -8,7 +8,7 @@ %endif Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} - %if status['type']=="openended": + %if status['type']=="openended" and status['state'] in ['done', 'post_assessment']: From 5150d6cdeec2664773acb7ac67d3f20461a120aa Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:28:02 -0500 Subject: [PATCH 126/171] Add in better status indicators --- common/lib/xmodule/xmodule/openendedchild.py | 3 ++- lms/templates/combined_open_ended_status.html | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 73bd8f3957..5fccdee4f6 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -259,7 +259,8 @@ class OpenEndedChild(): def is_last_response_correct(self): score=self.get_score() - return self.is_submission_correct(score) + correctness = 'correct' if self.is_submission_correct(score) else 'incorrect' + return diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index 9d51bd67fd..a653a84d13 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -8,6 +8,15 @@ %endif Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} + % if state == 'initial': + Unanswered + % elif state in ['done', 'post_assessment'] and correct == 'correct': + Correct + % elif state in ['done', 'post_assessment'] and correct == 'incorrect': + Incorrect + % elif state == 'assessing': + Submitted for grading + % endif %if status['type']=="openended" and status['state'] in ['done', 'post_assessment']:
Show results from step ${status['task_number']} From 3bf532e199e62a942ccbb2f3a6164bbb2f142015 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:45:07 -0500 Subject: [PATCH 127/171] Check marks in each step --- .../css/combinedopenended/display.scss | 29 +++++++++++++++++++ common/lib/xmodule/xmodule/openendedchild.py | 4 +-- lms/templates/combined_open_ended_status.html | 17 ++++++----- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 7f52cd9623..be86757aee 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -67,6 +67,35 @@ section.combined-open-ended-status { font-size: 1em; padding-top: 10px; } + + span { + &.unanswered { + @include inline-block(); + background: url('../images/unanswered-icon.png') center center no-repeat; + height: 14px; + position: relative; + width: 14px; + float: right; + } + + &.correct { + @include inline-block(); + background: url('../images/correct-icon.png') center center no-repeat; + height: 20px; + position: relative; + width: 25px; + float: right; + } + + &.incorrect { + @include inline-block(); + background: url('../images/incorrect-icon.png') center center no-repeat; + height: 20px; + width: 20px; + position: relative; + float: right; + } + } } div.result-container { diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 5fccdee4f6..5d69323e4a 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -258,9 +258,9 @@ class OpenEndedChild(): return correct def is_last_response_correct(self): - score=self.get_score() + score=self.get_score()['score'] correctness = 'correct' if self.is_submission_correct(score) else 'incorrect' - return + return correctness diff --git a/lms/templates/combined_open_ended_status.html b/lms/templates/combined_open_ended_status.html index a653a84d13..34a5dd0d79 100644 --- a/lms/templates/combined_open_ended_status.html +++ b/lms/templates/combined_open_ended_status.html @@ -8,15 +8,16 @@ %endif Step ${status['task_number']} (${status['human_state']}) : ${status['score']} / ${status['max_score']} - % if state == 'initial': - Unanswered - % elif state in ['done', 'post_assessment'] and correct == 'correct': - Correct - % elif state in ['done', 'post_assessment'] and correct == 'incorrect': - Incorrect - % elif state == 'assessing': - Submitted for grading + % if status['state'] == 'initial': + + % elif status['state'] in ['done', 'post_assessment'] and status['correct'] == 'correct': + + % elif status['state'] in ['done', 'post_assessment'] and status['correct'] == 'incorrect': + + % elif status['state'] == 'assessing': + % endif + %if status['type']=="openended" and status['state'] in ['done', 'post_assessment']:
Show results from step ${status['task_number']} From 9e14e22c91e28376424b2fb41ac642a82d3642f3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 15:53:36 -0500 Subject: [PATCH 128/171] Move prompt and rubric to combined open ended instead of defining them in each task --- .../xmodule/combined_open_ended_module.py | 12 +++++++++--- common/lib/xmodule/xmodule/open_ended_module.py | 17 ++++++----------- common/lib/xmodule/xmodule/openendedchild.py | 3 +++ .../xmodule/xmodule/self_assessment_module.py | 2 -- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 957b978407..4788c43382 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -98,6 +98,8 @@ class CombinedOpenEndedModule(XModule): self.static_data = { 'max_score' : self._max_score, 'max_attempts' : self.max_attempts, + 'prompt' : definition['prompt'], + 'rubric' : definition['rubric'] } self.task_xml=definition['task_xml'] @@ -371,16 +373,20 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): 'hintprompt': 'some-html' } """ - expected_children = ['task'] + expected_children = ['task', 'rubric', 'prompt'] for child in expected_children: if len(xml_object.xpath(child)) == 0 : raise ValueError("Combined Open Ended definition must include at least one '{0}' tag".format(child)) - def parse(k): + 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)))] - return {'task_xml': parse('task')} + def parse(k): + """Assumes that xml_object has child k""" + return xml_object.xpath(k)[0] + + return {'task_xml': parse_task('task'), 'prompt' : parse('prompt'), 'rubric' : parse('rubric')} def definition_to_xml(self, resource_fs): diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 9cdbdd54e2..b795db8228 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -40,8 +40,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): oeparam = definition['oeparam'] - prompt = definition['prompt'] - rubric = definition['rubric'] self.url = definition.get('url', None) self.queue_name = definition.get('queuename', self.DEFAULT_QUEUE) @@ -53,12 +51,12 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if oeparam is None: raise ValueError("No oeparam found in problem xml.") - if prompt is None: + if self.prompt is None: raise ValueError("No prompt found in problem xml.") - if rubric is None: + if self.rubric is None: raise ValueError("No rubric found in problem xml.") - self._parse(oeparam, prompt, rubric, system) + self._parse(oeparam, self.prompt, self.rubric, system) if self.created=="True" and self.state == self.ASSESSING: self.created="False" @@ -530,7 +528,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): } """ - for child in ['openendedrubric', 'prompt', 'openendedparam']: + for child in ['openendedparam']: if len(xml_object.xpath(child)) != 1: raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child)) @@ -538,10 +536,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """Assumes that xml_object has child k""" return xml_object.xpath(k)[0] - return {'rubric': parse('openendedrubric'), - 'prompt': parse('prompt'), - 'oeparam': parse('openendedparam'), - } + return {'oeparam': parse('openendedparam'),} def definition_to_xml(self, resource_fs): @@ -553,7 +548,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): child_node = etree.fromstring(child_str) elt.append(child_node) - for child in ['openendedrubric', 'prompt', 'openendedparam']: + for child in ['openendedparam']: add_child(child) return elt diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 5d69323e4a..4a81703919 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -127,6 +127,9 @@ class OpenEndedChild(): self.attempts = instance_state.get('attempts', 0) self.max_attempts = static_data['max_attempts'] + self.prompt = static_data['prompt'] + self.rubric = static_data['rubric'] + # 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'] diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 88c47f92ef..7050ff991b 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -33,8 +33,6 @@ log = logging.getLogger("mitx.courseware") class SelfAssessmentModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): - self.rubric = definition['rubric'] - self.prompt = definition['prompt'] self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] From 3aa0daaa764176823b9d9e2811f73d7b4bfcdf05 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 16:00:30 -0500 Subject: [PATCH 129/171] Add in proper prompt and rubric parsing --- common/lib/xmodule/xmodule/open_ended_module.py | 1 + common/lib/xmodule/xmodule/self_assessment_module.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index b795db8228..3047c86888 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -76,6 +76,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): prompt_string = stringify_children(prompt) rubric_string = stringify_children(rubric) self.prompt=prompt_string + self.rubric=rubric_string grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 7050ff991b..db082a8b4f 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -35,6 +35,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] + self.prompt = stringify_children(prompt) + self.rubric = stringify_children(rubric) def get_html(self, system): #set context variables and render template @@ -270,7 +272,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): 'hintprompt': 'some-html' } """ - expected_children = ['rubric', 'prompt', 'submitmessage', 'hintprompt'] + expected_children = ['submitmessage', 'hintprompt'] for child in expected_children: if len(xml_object.xpath(child)) != 1: raise ValueError("Self assessment definition must include exactly one '{0}' tag".format(child)) @@ -279,9 +281,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """Assumes that xml_object has child k""" return stringify_children(xml_object.xpath(k)[0]) - return {'rubric': parse('rubric'), - 'prompt': parse('prompt'), - 'submitmessage': parse('submitmessage'), + return {'submitmessage': parse('submitmessage'), 'hintprompt': parse('hintprompt'), } @@ -294,7 +294,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): child_node = etree.fromstring(child_str) elt.append(child_node) - for child in ['rubric', 'prompt', 'submitmessage', 'hintprompt']: + for child in ['submitmessage', 'hintprompt']: add_child(child) return elt From dcb33f1d5f2d2189968e813764ba4f97acca09ee Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 16:04:09 -0500 Subject: [PATCH 130/171] Fix prompt and rubric passing --- common/lib/xmodule/xmodule/self_assessment_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index db082a8b4f..52701a8cf1 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -35,8 +35,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): def setup_response(self, system, location, definition, descriptor): self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] - self.prompt = stringify_children(prompt) - self.rubric = stringify_children(rubric) + self.prompt = stringify_children(self.prompt) + self.rubric = stringify_children(self.rubric) def get_html(self, system): #set context variables and render template From b6a49f33ad0d678265bff5a826b6e2b678711e31 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 16:31:33 -0500 Subject: [PATCH 131/171] parse out min and max score to advance --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 11 +++++++---- common/lib/xmodule/xmodule/open_ended_module.py | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4788c43382..0e9874ec4f 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -82,7 +82,6 @@ class CombinedOpenEndedModule(XModule): # element. # Scores are on scale from 0 to max_score system.set('location', location) - log.debug(system.location) self.current_task_number = instance_state.get('current_task_number', 0) self.task_states= instance_state.get('task_states', []) @@ -147,7 +146,13 @@ class CombinedOpenEndedModule(XModule): children=self.child_modules() self.current_task_descriptor=children['descriptors'][current_task_type](self.system) - self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree.fromstring(self.current_task_xml),self.system) + etree_xml=etree.fromstring(self.current_task_xml) + min_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',0)) + max_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',self._max_score)) + if self.current_task_number>0: + last_response_data=self.get_last_response(self.current_task_number-1) + + 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=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data) self.task_states.append(self.current_task.get_instance_state()) @@ -165,8 +170,6 @@ class CombinedOpenEndedModule(XModule): current_task_state=self.overwrite_state(current_task_state) self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) - log.debug(self.current_task.get_instance_state()) - log.debug(self.get_instance_state()) return True def get_context(self): diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 3047c86888..0e16156f1a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -528,7 +528,6 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): 'oeparam': 'some-html' } """ - for child in ['openendedparam']: if len(xml_object.xpath(child)) != 1: raise ValueError("Open Ended definition must include exactly one '{0}' tag".format(child)) From 1ae94ce6366f2dc00a7159a9e06d4578c262bf8d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 16:45:21 -0500 Subject: [PATCH 132/171] Hopefully allow for submission to be reset early --- .../xmodule/combined_open_ended_module.py | 33 +++++++++++++++---- .../js/src/combinedopenended/display.coffee | 11 ++++--- common/lib/xmodule/xmodule/openendedchild.py | 2 +- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 0e9874ec4f..d31da49978 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -147,10 +147,11 @@ class CombinedOpenEndedModule(XModule): self.current_task_descriptor=children['descriptors'][current_task_type](self.system) etree_xml=etree.fromstring(self.current_task_xml) - min_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',0)) - max_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',self._max_score)) + if self.current_task_number>0: - last_response_data=self.get_last_response(self.current_task_number-1) + allow_reset=self.check_allow_reset() + if allow_reset: + return False 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: @@ -172,6 +173,17 @@ class CombinedOpenEndedModule(XModule): return True + def check_allow_reset(self): + allow_reset=False + if self.current_task_number>0: + last_response_data=self.get_last_response(self.current_task_number-1) + current_response_data=self.get_last_response(self.current_task_number) + + if current_response_data['min_score_to_attempt']>last_response_data['score'] or current_response_data['max_score_to_attempt'] if response.success @child_state = 'done' - @allow_reset = response.allow_reset @rebind() else @errors_area.html(response.error) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 4a81703919..304271c620 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -200,7 +200,7 @@ class OpenEndedChild(): def _allow_reset(self): """Can the module be reset?""" - return self.state == self.DONE and self.attempts < self.max_attempts + return (self.state == self.DONE and self.attempts < self.max_attempts) def max_score(self): """ From b13f94798fd3ff5fe37c5c509a9b94c78de671f1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 17:02:56 -0500 Subject: [PATCH 133/171] Add in allow reset action --- .../xmodule/combined_open_ended_module.py | 39 ++++++++++++------- .../js/src/combinedopenended/display.coffee | 2 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index d31da49978..f9c610d51a 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -88,6 +88,7 @@ class CombinedOpenEndedModule(XModule): self.state = instance_state.get('state', 'initial') self.attempts = instance_state.get('attempts', 0) + self.allow_reset = instance_state.get('ready_to_reset', False) self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) # Used for progress / grading. Currently get credit just for @@ -149,8 +150,8 @@ class CombinedOpenEndedModule(XModule): etree_xml=etree.fromstring(self.current_task_xml) if self.current_task_number>0: - allow_reset=self.check_allow_reset() - if allow_reset: + self.allow_reset=self.check_allow_reset() + if self.allow_reset: return False self.current_task_parsed_xml=self.current_task_descriptor.definition_from_xml(etree_xml,self.system) @@ -174,15 +175,15 @@ class CombinedOpenEndedModule(XModule): return True def check_allow_reset(self): - allow_reset=False if self.current_task_number>0: last_response_data=self.get_last_response(self.current_task_number-1) - current_response_data=self.get_last_response(self.current_task_number) + current_response_data=self.get_current_attributes(self.current_task_number) - 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']=(len(self.task_xml)): self.state=self.DONE @@ -334,6 +343,7 @@ class CombinedOpenEndedModule(XModule): 'error': 'Too many attempts.' } self.state=self.INITIAL + self.allow_reset=False for i in xrange(0,len(self.task_xml)): self.current_task_number=i self.setup_next_task(reset=True) @@ -354,6 +364,7 @@ class CombinedOpenEndedModule(XModule): 'state': self.state, 'task_states': self.task_states, 'attempts': self.attempts, + 'ready_to_reset' : self.allow_reset, } return json.dumps(state) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 0d420a8514..3929ebe78a 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -79,7 +79,7 @@ class @CombinedOpenEnded if @child_type=="openended" @skip_button.hide() - if @allow_reset + if @allow_reset=="True" @reset_button.show() @submit_button.hide() @answer_area.attr("disabled", true) From adcbfbb6035e748cf4b0cecaaf2e4fee3d563113 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 18:00:46 -0500 Subject: [PATCH 134/171] Fix reset --- .../xmodule/combined_open_ended_module.py | 55 ++++++++++--------- .../js/src/combinedopenended/display.coffee | 3 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index f9c610d51a..3c756f6834 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -142,6 +142,12 @@ class CombinedOpenEndedModule(XModule): current_task_state=self.task_states[self.current_task_number] 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.current_task_number=self.current_task_number-1 + current_task_type=self.get_tag_name(self.current_task_xml) children=self.child_modules() @@ -149,11 +155,6 @@ class CombinedOpenEndedModule(XModule): self.current_task_descriptor=children['descriptors'][current_task_type](self.system) etree_xml=etree.fromstring(self.current_task_xml) - if self.current_task_number>0: - self.allow_reset=self.check_allow_reset() - if self.allow_reset: - return False - 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=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data) @@ -175,13 +176,14 @@ class CombinedOpenEndedModule(XModule): return True def check_allow_reset(self): - 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) + if not self.allow_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) - 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']=(len(self.task_xml)): - self.state=self.DONE - self.current_task_number=len(self.task_xml)-1 - else: - self.state=self.INITIAL - changed=True - self.setup_next_task() + 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: + self.current_task_number+=1 + if self.current_task_number>=(len(self.task_xml)): + self.state=self.DONE + self.current_task_number=len(self.task_xml)-1 + else: + self.state=self.INITIAL + changed=True + self.setup_next_task() return changed def update_task_states_ajax(self,return_html): @@ -335,7 +336,8 @@ class CombinedOpenEndedModule(XModule): (error only present if not success) """ if self.state != self.DONE: - return self.out_of_sync_error(get) + if not self.allow_reset: + return self.out_of_sync_error(get) if self.attempts > self.max_attempts: return { @@ -350,6 +352,7 @@ class CombinedOpenEndedModule(XModule): 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.setup_next_task() return {'success': True, 'html' : self.get_html_nonsystem()} diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 3929ebe78a..a7e01e8a9b 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -187,7 +187,7 @@ class @CombinedOpenEnded reset: (event) => event.preventDefault() - if @child_state == 'done' + if @child_state == 'done' or @allow_reset=="True" $.postWithPrefix "#{@ajax_url}/reset", {}, (response) => if response.success @answer_area.val('') @@ -196,6 +196,7 @@ class @CombinedOpenEnded @message_wrapper.html('') @child_state = 'initial' @combined_open_ended.after(response.html).remove() + @allow_reset="False" @reinitialize(@element) @rebind() @reset_button.hide() From bc97a507e047cb8fca8626b303e1507fa57ae930 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 18:05:01 -0500 Subject: [PATCH 135/171] Next step logic --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 3c756f6834..ee36690b1c 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -326,7 +326,7 @@ class CombinedOpenEndedModule(XModule): def next_problem(self, get): self.update_task_states() - return {'success' : True, 'html' : self.get_html_nonsystem()} + return {'success' : True, 'html' : self.get_html_nonsystem(), 'allow_reset' : self.allow_reset} def reset(self, get): """ diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index a7e01e8a9b..682ba983bd 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -218,7 +218,10 @@ class @CombinedOpenEnded @reinitialize(@element) @rebind() @next_problem_button.hide() - @gentle_alert "Moved to next step." + if response.allow_reset=="False" + @gentle_alert "Moved to next step." + else + @gentle_alert "Your score did not meet the criteria to move to the next step." else @errors_area.html(response.error) else From 38a81b461f413d8a7e4cc00a7793d0de8d8b6894 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 18:33:41 -0500 Subject: [PATCH 136/171] Remove open ended grading stuff, fix JS variable --- common/lib/capa/capa/inputtypes.py | 48 -- common/lib/capa/capa/responsetypes.py | 433 +----------------- .../xmodule/js/src/capa/display.coffee | 30 -- .../js/src/combinedopenended/display.coffee | 2 +- .../lib/xmodule/xmodule/open_ended_module.py | 2 +- 5 files changed, 3 insertions(+), 512 deletions(-) diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index e3eb47acc5..1d3646fefc 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -735,51 +735,3 @@ class ChemicalEquationInput(InputTypeBase): registry.register(ChemicalEquationInput) #----------------------------------------------------------------------------- - -class OpenEndedInput(InputTypeBase): - """ - A text area input for code--uses codemirror, does syntax highlighting, special tab handling, - etc. - """ - - template = "openendedinput.html" - tags = ['openendedinput'] - - # pulled out for testing - submitted_msg = ("Feedback not yet available. Reload to check again. " - "Once the problem is graded, this message will be " - "replaced with the grader's feedback.") - - @classmethod - def get_attributes(cls): - """ - Convert options to a convenient format. - """ - return [Attribute('rows', '30'), - Attribute('cols', '80'), - Attribute('hidden', ''), - ] - - def setup(self): - """ - Implement special logic: handle queueing state, and default input. - """ - # if no student input yet, then use the default input given by the problem - if not self.value: - self.value = self.xml.text - - # Check if problem has been queued - self.queue_len = 0 - # Flag indicating that the problem has been queued, 'msg' is length of queue - if self.status == 'incomplete': - self.status = 'queued' - self.queue_len = self.msg - self.msg = self.submitted_msg - - def _extra_context(self): - """Defined queue_len, add it """ - return {'queue_len': self.queue_len,} - -registry.register(OpenEndedInput) - -#----------------------------------------------------------------------------- diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 1bc34b70a3..3d97cb0bea 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -1815,436 +1815,6 @@ class ImageResponse(LoncapaResponse): return (dict([(ie.get('id'), ie.get('rectangle')) for ie in self.ielements]), dict([(ie.get('id'), ie.get('regions')) for ie in self.ielements])) #----------------------------------------------------------------------------- - -class OpenEndedResponse(LoncapaResponse): - """ - Grade student open ended responses using an external grading system, - accessed through the xqueue system. - - Expects 'xqueue' dict in ModuleSystem with the following keys that are - needed by OpenEndedResponse: - - system.xqueue = { 'interface': XqueueInterface object, - 'callback_url': Per-StudentModule callback URL - where results are posted (string), - } - - External requests are only submitted for student submission grading - (i.e. and not for getting reference answers) - - By default, uses the OpenEndedResponse.DEFAULT_QUEUE queue. - """ - - DEFAULT_QUEUE = 'open-ended' - DEFAULT_MESSAGE_QUEUE = 'open-ended-message' - response_tag = 'openendedresponse' - allowed_inputfields = ['openendedinput'] - max_inputfields = 1 - - def setup_response(self): - ''' - Configure OpenEndedResponse from XML. - ''' - xml = self.xml - self.url = xml.get('url', None) - self.queue_name = xml.get('queuename', self.DEFAULT_QUEUE) - self.message_queue_name = xml.get('message-queuename', self.DEFAULT_MESSAGE_QUEUE) - - # The openendedparam tag encapsulates all grader settings - oeparam = self.xml.find('openendedparam') - prompt = self.xml.find('prompt') - rubric = self.xml.find('openendedrubric') - - #This is needed to attach feedback to specific responses later - self.submission_id=None - self.grader_id=None - - if oeparam is None: - raise ValueError("No oeparam found in problem xml.") - if prompt is None: - raise ValueError("No prompt found in problem xml.") - if rubric is None: - raise ValueError("No rubric found in problem xml.") - - self._parse(oeparam, prompt, rubric) - - @staticmethod - def stringify_children(node): - """ - Modify code from stringify_children in xmodule. Didn't import directly - in order to avoid capa depending on xmodule (seems to be avoided in - code) - """ - parts=[node.text if node.text is not None else ''] - for p in node.getchildren(): - parts.append(etree.tostring(p, with_tail=True, encoding='unicode')) - - return ' '.join(parts) - - def _parse(self, oeparam, prompt, rubric): - ''' - Parse OpenEndedResponse XML: - self.initial_display - self.payload - dict containing keys -- - 'grader' : path to grader settings file, 'problem_id' : id of the problem - - self.answer - What to display when show answer is clicked - ''' - # Note that OpenEndedResponse is agnostic to the specific contents of grader_payload - prompt_string = self.stringify_children(prompt) - rubric_string = self.stringify_children(rubric) - - grader_payload = oeparam.find('grader_payload') - grader_payload = grader_payload.text if grader_payload is not None else '' - - #Update grader payload with student id. If grader payload not json, error. - try: - parsed_grader_payload = json.loads(grader_payload) - # NOTE: self.system.location is valid because the capa_module - # __init__ adds it (easiest way to get problem location into - # response types) - except TypeError, ValueError: - log.exception("Grader payload %r is not a json object!", grader_payload) - - self.initial_display = find_with_default(oeparam, 'initial_display', '') - self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') - - parsed_grader_payload.update({ - 'location' : self.system.location, - 'course_id' : self.system.course_id, - 'prompt' : prompt_string, - 'rubric' : rubric_string, - 'initial_display' : self.initial_display, - 'answer' : self.answer, - }) - updated_grader_payload = json.dumps(parsed_grader_payload) - - self.payload = {'grader_payload': updated_grader_payload} - - try: - self.max_score = int(find_with_default(oeparam, 'max_score', 1)) - except ValueError: - self.max_score = 1 - - def handle_message_post(self,event_info): - """ - Handles a student message post (a reaction to the grade they received from an open ended grader type) - Returns a boolean success/fail and an error message - """ - survey_responses=event_info['survey_responses'] - for tag in ['feedback', 'submission_id', 'grader_id', 'score']: - if tag not in survey_responses: - return False, "Could not find needed tag {0}".format(tag) - try: - submission_id=int(survey_responses['submission_id']) - grader_id = int(survey_responses['grader_id']) - feedback = str(survey_responses['feedback'].encode('ascii', 'ignore')) - score = int(survey_responses['score']) - except: - error_message=("Could not parse submission id, grader id, " - "or feedback from message_post ajax call. Here is the message data: {0}".format(survey_responses)) - log.exception(error_message) - return False, "There was an error saving your feedback. Please contact course staff." - - qinterface = self.system.xqueue['interface'] - qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) - anonymous_student_id = self.system.anonymous_student_id - queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + - anonymous_student_id + - self.answer_id) - - xheader = xqueue_interface.make_xheader( - lms_callback_url=self.system.xqueue['callback_url'], - lms_key=queuekey, - queue_name=self.message_queue_name - ) - - student_info = {'anonymous_student_id': anonymous_student_id, - 'submission_time': qtime, - } - contents= { - 'feedback' : feedback, - 'submission_id' : submission_id, - 'grader_id' : grader_id, - 'score': score, - 'student_info' : json.dumps(student_info), - } - - (error, msg) = qinterface.send_to_queue(header=xheader, - body=json.dumps(contents)) - - #Convert error to a success value - success=True - if error: - success=False - - return success, "Successfully submitted your feedback." - - def get_score(self, student_answers): - - try: - submission = student_answers[self.answer_id] - except KeyError: - msg = ('Cannot get student answer for answer_id: {0}. student_answers {1}' - .format(self.answer_id, student_answers)) - log.exception(msg) - raise LoncapaProblemError(msg) - - # Prepare xqueue request - #------------------------------------------------------------ - - qinterface = self.system.xqueue['interface'] - qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) - - anonymous_student_id = self.system.anonymous_student_id - - # Generate header - queuekey = xqueue_interface.make_hashkey(str(self.system.seed) + qtime + - anonymous_student_id + - self.answer_id) - - xheader = xqueue_interface.make_xheader(lms_callback_url=self.system.xqueue['callback_url'], - lms_key=queuekey, - queue_name=self.queue_name) - - self.context.update({'submission': submission}) - - contents = self.payload.copy() - - # Metadata related to the student submission revealed to the external grader - student_info = {'anonymous_student_id': anonymous_student_id, - 'submission_time': qtime, - } - - #Update contents with student response and student info - contents.update({ - 'student_info': json.dumps(student_info), - 'student_response': submission, - 'max_score' : self.max_score, - }) - - # Submit request. When successful, 'msg' is the prior length of the queue - (error, msg) = qinterface.send_to_queue(header=xheader, - body=json.dumps(contents)) - - # State associated with the queueing request - queuestate = {'key': queuekey, - 'time': qtime,} - - cmap = CorrectMap() - if error: - cmap.set(self.answer_id, queuestate=None, - msg='Unable to deliver your submission to grader. (Reason: {0}.)' - ' Please try again later.'.format(msg)) - else: - # Queueing mechanism flags: - # 1) Backend: Non-null CorrectMap['queuestate'] indicates that - # the problem has been queued - # 2) Frontend: correctness='incomplete' eventually trickles down - # through inputtypes.textbox and .filesubmission to inform the - # browser that the submission is queued (and it could e.g. poll) - cmap.set(self.answer_id, queuestate=queuestate, - correctness='incomplete', msg=msg) - - return cmap - - def update_score(self, score_msg, oldcmap, queuekey): - log.debug(score_msg) - score_msg = self._parse_score_msg(score_msg) - if not score_msg.valid: - oldcmap.set(self.answer_id, - msg = 'Invalid grader reply. Please contact the course staff.') - return oldcmap - - correctness = 'correct' if score_msg.correct else 'incorrect' - - # TODO: Find out how this is used elsewhere, if any - self.context['correct'] = correctness - - # Replace 'oldcmap' with new grading results if queuekey matches. If queuekey - # does not match, we keep waiting for the score_msg whose key actually matches - if oldcmap.is_right_queuekey(self.answer_id, queuekey): - # Sanity check on returned points - points = score_msg.points - if points < 0: - points = 0 - - # Queuestate is consumed, so reset it to None - oldcmap.set(self.answer_id, npoints=points, correctness=correctness, - msg = score_msg.msg.replace(' ', ' '), queuestate=None) - else: - log.debug('OpenEndedResponse: queuekey {0} does not match for answer_id={1}.'.format( - queuekey, self.answer_id)) - - return oldcmap - - def get_answers(self): - anshtml = '
{0}
'.format(self.answer) - return {self.answer_id: anshtml} - - def get_initial_display(self): - return {self.answer_id: self.initial_display} - - def _convert_longform_feedback_to_html(self, response_items): - """ - Take in a dictionary, and return html strings for display to student. - Input: - response_items: Dictionary with keys success, feedback. - if success is True, feedback should be a dictionary, with keys for - types of feedback, and the corresponding feedback values. - if success is False, feedback is actually an error string. - - NOTE: this will need to change when we integrate peer grading, because - that will have more complex feedback. - - Output: - String -- html that can be displayed to the student. - """ - - # We want to display available feedback in a particular order. - # This dictionary specifies which goes first--lower first. - priorities = {# These go at the start of the feedback - 'spelling': 0, - 'grammar': 1, - # needs to be after all the other feedback - 'markup_text': 3} - - default_priority = 2 - - def get_priority(elt): - """ - Args: - elt: a tuple of feedback-type, feedback - Returns: - the priority for this feedback type - """ - return priorities.get(elt[0], default_priority) - - def encode_values(feedback_type,value): - feedback_type=str(feedback_type).encode('ascii', 'ignore') - if not isinstance(value,basestring): - value=str(value) - value=value.encode('ascii', 'ignore') - return feedback_type,value - - def format_feedback(feedback_type, value): - feedback_type,value=encode_values(feedback_type,value) - feedback= """ -
- {value} -
- """.format(feedback_type=feedback_type, value=value) - return feedback - - def format_feedback_hidden(feedback_type , value): - feedback_type,value=encode_values(feedback_type,value) - feedback = """ - - """.format(feedback_type=feedback_type, value=value) - return feedback - - # TODO (vshnayder): design and document the details of this format so - # that we can do proper escaping here (e.g. are the graders allowed to - # include HTML?) - - for tag in ['success', 'feedback', 'submission_id', 'grader_id']: - if tag not in response_items: - return format_feedback('errors', 'Error getting feedback') - - feedback_items = response_items['feedback'] - try: - feedback = json.loads(feedback_items) - except (TypeError, ValueError): - log.exception("feedback_items have invalid json %r", feedback_items) - return format_feedback('errors', 'Could not parse feedback') - - if response_items['success']: - if len(feedback) == 0: - return format_feedback('errors', 'No feedback available') - - feedback_lst = sorted(feedback.items(), key=get_priority) - feedback_list_part1 = u"\n".join(format_feedback(k, v) for k, v in feedback_lst) - else: - feedback_list_part1 = format_feedback('errors', response_items['feedback']) - - feedback_list_part2=(u"\n".join([format_feedback_hidden(feedback_type,value) - for feedback_type,value in response_items.items() - if feedback_type in ['submission_id', 'grader_id']])) - - return u"\n".join([feedback_list_part1,feedback_list_part2]) - - def _format_feedback(self, response_items): - """ - Input: - Dictionary called feedback. Must contain keys seen below. - Output: - Return error message or feedback template - """ - - feedback = self._convert_longform_feedback_to_html(response_items) - - if not response_items['success']: - return self.system.render_template("open_ended_error.html", - {'errors' : feedback}) - - feedback_template = self.system.render_template("open_ended_feedback.html", { - 'grader_type': response_items['grader_type'], - 'score': "{0} / {1}".format(response_items['score'], self.max_score), - 'feedback': feedback, - }) - - return feedback_template - - - def _parse_score_msg(self, score_msg): - """ - Grader reply is a JSON-dump of the following dict - { 'correct': True/False, - 'score': Numeric value (floating point is okay) to assign to answer - 'msg': grader_msg - 'feedback' : feedback from grader - } - - Returns (valid_score_msg, correct, score, msg): - valid_score_msg: Flag indicating valid score_msg format (Boolean) - correct: Correctness of submission (Boolean) - score: Points to be assigned (numeric, can be float) - """ - fail = ScoreMessage(valid=False, correct=False, points=0, msg='') - try: - score_result = json.loads(score_msg) - except (TypeError, ValueError): - log.error("External grader message should be a JSON-serialized dict." - " Received score_msg = {0}".format(score_msg)) - return fail - - if not isinstance(score_result, dict): - log.error("External grader message should be a JSON-serialized dict." - " Received score_result = {0}".format(score_result)) - return fail - - for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']: - if tag not in score_result: - log.error("External grader message is missing required tag: {0}" - .format(tag)) - return fail - - feedback = self._format_feedback(score_result) - self.submission_id=score_result['submission_id'] - self.grader_id=score_result['grader_id'] - - # HACK: for now, just assume it's correct if you got more than 2/3. - # Also assumes that score_result['score'] is an integer. - score_ratio = int(score_result['score']) / float(self.max_score) - correct = (score_ratio >= 0.66) - - #Currently ignore msg and only return feedback (which takes the place of msg) - return ScoreMessage(valid=True, correct=correct, - points=score_result['score'], msg=feedback) - -#----------------------------------------------------------------------------- # TEMPORARY: List of all response subclasses # FIXME: To be replaced by auto-registration @@ -2261,5 +1831,4 @@ __all__ = [CodeResponse, ChoiceResponse, MultipleChoiceResponse, TrueFalseResponse, - JavascriptResponse, - OpenEndedResponse] + JavascriptResponse] diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee index ba746fecb8..1c0ace9e59 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee @@ -25,7 +25,6 @@ class @Problem @$('section.action input.reset').click @reset @$('section.action input.show').click @show @$('section.action input.save').click @save - @$('section.evaluation input.submit-message').click @message_post # Collapsibles Collapsible.setCollapsibles(@el) @@ -198,35 +197,6 @@ class @Problem else @gentle_alert response.success - message_post: => - Logger.log 'message_post', @answers - - fd = new FormData() - feedback = @$('section.evaluation textarea.feedback-on-feedback')[0].value - submission_id = $('div.external-grader-message div.submission_id')[0].innerHTML - grader_id = $('div.external-grader-message div.grader_id')[0].innerHTML - score = $(".evaluation-scoring input:radio[name='evaluation-score']:checked").val() - fd.append('feedback', feedback) - fd.append('submission_id', submission_id) - fd.append('grader_id', grader_id) - if(!score) - @gentle_alert "You need to pick a rating before you can submit." - return - else - fd.append('score', score) - - - settings = - type: "POST" - data: fd - processData: false - contentType: false - success: (response) => - @gentle_alert response.message - @$('section.evaluation').slideToggle() - - $.ajaxWithPrefix("#{@url}/message_post", settings) - reset: => Logger.log 'problem_reset', @answers $.postWithPrefix "#{@url}/problem_reset", id: @id, (response) => diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 682ba983bd..8a5ef42270 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -218,7 +218,7 @@ class @CombinedOpenEnded @reinitialize(@element) @rebind() @next_problem_button.hide() - if response.allow_reset=="False" + if !response.allow_reset @gentle_alert "Moved to next step." else @gentle_alert "Your score did not meet the criteria to move to the next step." diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 0e16156f1a..024422773d 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -245,7 +245,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): that will have more complex feedback. Output: - String -- html that can be displayed to the student. + String -- html that can be displayincorrect-icon.pnged to the student. """ # We want to display available feedback in a particular order. From 2f841c8a334d329ea5d281668d714abd21c4d8d1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 18:55:28 -0500 Subject: [PATCH 137/171] Document combined open ended module --- .../xmodule/combined_open_ended_module.py | 144 ++++++++++++++++-- 1 file changed, 128 insertions(+), 16 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index ee36690b1c..a639d6997a 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -36,6 +36,10 @@ MAX_ATTEMPTS = 10000 MAX_SCORE = 1 class CombinedOpenEndedModule(XModule): + """ + This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc). + It transitions between problems, and support arbitrary ordering. + """ STATE_VERSION = 1 # states @@ -59,16 +63,37 @@ class CombinedOpenEndedModule(XModule): instance_state, shared_state, **kwargs) """ - Definition file should have multiple task blocks: + Definition file should have one or many task blocks, a rubric block, and a prompt block: Sample file: - - - + + + Blah blah rubric. + + + Some prompt. + + + + What hint about this problem would you give to someone? + + + Save Succcesful. Thanks for participating! + + + + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + + + """ # Load instance state @@ -77,17 +102,19 @@ class CombinedOpenEndedModule(XModule): else: instance_state = {} - # History is a list of tuples of (answer, score, hint), where hint may be - # 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 + #We need to set the location here so the child modules can use it system.set('location', location) - self.current_task_number = instance_state.get('current_task_number', 0) - self.task_states= instance_state.get('task_states', []) + #Tells the system which xml definition to load + self.current_task_number = instance_state.get('current_task_number', 0) + #This loads the states of the individual children + self.task_states= instance_state.get('task_states', []) + #Overall state of the combined open ended module self.state = instance_state.get('state', 'initial') self.attempts = instance_state.get('attempts', 0) + + #Allow reset is true if student has failed the criteria to move to the next child task self.allow_reset = instance_state.get('ready_to_reset', False) self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS)) @@ -95,6 +122,7 @@ class CombinedOpenEndedModule(XModule): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) + #Static data is passed to the child modules to render self.static_data = { 'max_score' : self._max_score, 'max_attempts' : self.max_attempts, @@ -106,10 +134,21 @@ class CombinedOpenEndedModule(XModule): self.setup_next_task() def get_tag_name(self, xml): + """ + Gets the tag name of a given xml block. + Input: XML string + Output: The name of the root tag + """ tag=etree.fromstring(xml).tag return tag def overwrite_state(self, current_task_state): + """ + Overwrites an instance state and sets the latest response to the current response. This is used + to ensure that the student response is carried over from the first child to the rest. + Input: Task state json string + Output: Task state json string + """ last_response_data=self.get_last_response(self.current_task_number-1) last_response = last_response_data['response'] @@ -122,6 +161,12 @@ class CombinedOpenEndedModule(XModule): return current_task_state def child_modules(self): + """ + Returns the functions associated with the child modules in a dictionary. This makes writing functions + simpler (saves code duplication) + Input: None + Output: A dictionary of dictionaries containing the descriptor functions and module functions + """ child_modules={ 'openended' : open_ended_module.OpenEndedModule, 'selfassessment' : self_assessment_module.SelfAssessmentModule, @@ -137,6 +182,12 @@ class CombinedOpenEndedModule(XModule): return children def setup_next_task(self, reset=False): + """ + Sets up the next task for the module. Creates an instance state if none exists, carries over the answer + from the last instance state to the next if needed. + Input: A boolean indicating whether or not the reset function is calling. + Output: Boolean True (not useful right now) + """ current_task_state=None if len(self.task_states)>self.current_task_number: current_task_state=self.task_states[self.current_task_number] @@ -176,6 +227,12 @@ class CombinedOpenEndedModule(XModule): return True def check_allow_reset(self): + """ + Checks to see if the student has passed the criteria to move to the next module. If not, sets + allow_reset to true and halts the student progress through the tasks. + Input: None + Output: the allow_reset attribute of the current module. + """ if not self.allow_reset: if self.current_task_number>0: last_response_data=self.get_last_response(self.current_task_number-1) @@ -188,6 +245,11 @@ class CombinedOpenEndedModule(XModule): return self.allow_reset def get_context(self): + """ + Generates a context dictionary that is used to render html. + Input: None + Output: A dictionary that can be rendered into the combined open ended template. + """ task_html=self.get_html_base() #set context variables and render template @@ -200,27 +262,47 @@ class CombinedOpenEndedModule(XModule): 'task_number' : self.current_task_number+1, 'status' : self.get_status(), } - log.debug(context) return context def get_html(self): + """ + Gets HTML for rendering. + Input: None + Output: rendered html + """ context=self.get_context() html = self.system.render_template('combined_open_ended.html', context) return html def get_html_nonsystem(self): + """ + Gets HTML for rendering via AJAX. Does not use system, because system contains some additional + html, which is not appropriate for returning via ajax calls. + Input: None + Output: HTML rendered directly via Mako + """ context=self.get_context() html = render_to_string('combined_open_ended.html', context) return html def get_html_base(self): + """ + Gets the HTML associated with the current child task + Input: None + Output: Child task HTML + """ self.update_task_states() html = self.current_task.get_html(self.system) return_html = rewrite_links(html, self.rewrite_content_links) return return_html def get_current_attributes(self, task_number): + """ + Gets the min and max score to attempt attributes of the specified task. + Input: The number of the task. + Output: The minimum and maximum scores needed to move on to the specified task. + """ task_xml=self.task_xml[task_number] etree_xml=etree.fromstring(task_xml) min_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',0)) @@ -228,6 +310,11 @@ class CombinedOpenEndedModule(XModule): return {'min_score_to_attempt' : min_score_to_attempt, 'max_score_to_attempt' : max_score_to_attempt} def get_last_response(self, task_number): + """ + Returns data associated with the specified task number, such as the last response, score, etc. + Input: The number of the task. + Output: A dictionary that contains information about the specified task. + """ last_response="" task_state = self.task_states[task_number] task_xml=self.task_xml[task_number] @@ -270,6 +357,11 @@ class CombinedOpenEndedModule(XModule): return last_response_dict def update_task_states(self): + """ + Updates the task state of the combined open ended module with the task state of the current child module. + Input: None + Output: boolean indicating whether or not the task state changed. + """ changed=False if not self.allow_reset: self.task_states[self.current_task_number] = self.current_task.get_instance_state() @@ -286,6 +378,11 @@ class CombinedOpenEndedModule(XModule): return changed def update_task_states_ajax(self,return_html): + """ + Runs the update task states function for ajax calls. Currently the same as update_task_states + Input: The html returned by the handle_ajax function of the child + Output: New html that should be rendered + """ changed=self.update_task_states() if changed: #return_html=self.get_html() @@ -293,6 +390,11 @@ class CombinedOpenEndedModule(XModule): return return_html def get_results(self, get): + """ + Gets the results of a given grader via ajax. + Input: AJAX get dictionary + Output: Dictionary to be rendered via ajax that contains the result html. + """ task_number=int(get['task_number']) self.update_task_states() response_dict=self.get_last_response(task_number) @@ -325,15 +427,19 @@ class CombinedOpenEndedModule(XModule): return json.dumps(d,cls=ComplexEncoder) def next_problem(self, get): + """ + Called via ajax to advance to the next problem. + Input: AJAX get request. + Output: Dictionary to be rendered + """ self.update_task_states() return {'success' : True, 'html' : self.get_html_nonsystem(), 'allow_reset' : self.allow_reset} def reset(self, get): """ - If resetting is allowed, reset the state. - - Returns {'success': bool, 'error': msg} - (error only present if not success) + If resetting is allowed, reset the state of the combined open ended module. + Input: AJAX get dictionary + Output: AJAX dictionary to tbe rendered """ if self.state != self.DONE: if not self.allow_reset: @@ -358,7 +464,9 @@ class CombinedOpenEndedModule(XModule): def get_instance_state(self): """ - Get the current score and state + Returns the current instance state. The module can be recreated from the instance state. + Input: None + Output: A dictionary containing the instance state. """ state = { @@ -373,6 +481,10 @@ class CombinedOpenEndedModule(XModule): return json.dumps(state) def get_status(self): + """ + Input: + Output: + """ status=[] for i in xrange(0,self.current_task_number+1): task_data = self.get_last_response(i) From 134c3f3db08250a3019591b287c868ee58ea1ec3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 18:57:37 -0500 Subject: [PATCH 138/171] Document open ended descriptor --- .../xmodule/xmodule/combined_open_ended_module.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index a639d6997a..4bc0c1fc85 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -482,8 +482,9 @@ class CombinedOpenEndedModule(XModule): def get_status(self): """ - Input: - Output: + Gets the status panel to be displayed at the top right. + Input: None + Output: The status html to be rendered """ status=[] for i in xrange(0,self.current_task_number+1): @@ -497,7 +498,7 @@ class CombinedOpenEndedModule(XModule): class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ - Module for adding self assessment questions to courses + Module for adding combined open ended questions """ mako_template = "widgets/html-edit.html" module_class = CombinedOpenEndedModule @@ -513,14 +514,13 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): @classmethod def definition_from_xml(cls, xml_object, system): """ - Pull out the rubric, prompt, and submitmessage into a dictionary. + Pull out the individual tasks, the rubric, and the prompt, and parse Returns: { 'rubric': 'some-html', 'prompt': 'some-html', - 'submitmessage': 'some-html' - 'hintprompt': 'some-html' + 'task_xml': dictionary of xml strings, } """ expected_children = ['task', 'rubric', 'prompt'] From 5303086a1044fef7ce5da357335a9387a99b5c31 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 19:07:34 -0500 Subject: [PATCH 139/171] Start commenting open ended module --- .../lib/xmodule/xmodule/open_ended_module.py | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 024422773d..e4008309cd 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -37,8 +37,18 @@ from datetime import datetime log = logging.getLogger("mitx.courseware") class OpenEndedModule(openendedchild.OpenEndedChild): - + """ + The open ended module supports all external open ended grader problems. + """ def setup_response(self, system, location, definition, descriptor): + """ + Sets up the response type. + @param system: Modulesystem object + @param location: The location of the problem + @param definition: The xml definition of the problem + @param descriptor: The OpenEndedDescriptor associated with this + @return: None + """ oeparam = definition['oeparam'] self.url = definition.get('url', None) @@ -106,6 +116,12 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.payload = {'grader_payload': updated_grader_payload} def skip_post_assessment(self, get, system): + """ + Ajax function that allows one to skip the post assessment phase + @param get: AJAX dictionary + @param system: ModuleSystem + @return: Success indicator + """ self.state=self.DONE return {'success' : True} @@ -172,6 +188,12 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return {'success' : success, 'msg' : "Successfully submitted your feedback."} def send_to_grader(self, submission, system): + """ + Send a given submission to the grader, via the xqueue + @param submission: The student submission to send to the grader + @param system: Modulesystem + @return: Boolean true (not useful right now) + """ # Prepare xqueue request #------------------------------------------------------------ @@ -214,6 +236,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return True def _update_score(self, score_msg, queuekey, system): + """ + Called by xqueue to update the score + @param score_msg: The message from xqueue + @param queuekey: The key sent by xqueue + @param system: Modulesystem + @return: Boolean True (not useful currently) + """ new_score_msg = self._parse_score_msg(score_msg) if not new_score_msg['valid']: score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' @@ -226,10 +255,18 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def get_answers(self): + """ + Gets and shows the answer for this problem. + @return: Answer html + """ anshtml = '
{0}
'.format(self.answer) return {self.answer_id: anshtml} def get_initial_display(self): + """ + Gets and shows the initial display for the input box. + @return: Initial display html + """ return {self.answer_id: self.initial_display} def _convert_longform_feedback_to_html(self, response_items): @@ -385,7 +422,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} def latest_post_assessment(self, short_feedback=False): - """None if not available""" + """ + Gets the latest feedback, parses, and returns + @param short_feedback: If the long feedback is wanted or not + @return: Returns formatted feedback + """ if not self.history: return "" @@ -397,6 +438,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return short_feedback if feedback_dict['valid'] else '' def format_feedback_with_evaluation(self,feedback): + """ + Renders a given html feedback into an evaluation template + @param feedback: HTML feedback + @return: Rendered html + """ context={'msg' : feedback, 'id' : "1", 'rows' : 50, 'cols' : 50} html= render_to_string('open_ended_evaluation.html', context) return html @@ -432,10 +478,22 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return json.dumps(d, cls=ComplexEncoder) def check_for_score(self, get, system): + """ + Checks to see if a score has been received yet. + @param get: AJAX get dictionary + @param system: Modulesystem (needed to align with other ajax functions) + @return: Returns the current state + """ state = self.state return {'state' : state} def save_answer(self, get, system): + """ + Saves a student answer + @param get: AJAX get dictionary + @param system: modulesystem + @return: Success indicator + """ if self.attempts > self.max_attempts: # If too many attempts, prevent student from saving answer and # seeing rubric. In normal use, students shouldn't see this because @@ -457,13 +515,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def update_score(self, get, system): """ - Delivers grading response (e.g. from asynchronous code checking) to - the capa problem, so its score can be updated - - 'get' must have a field 'response' which is a string that contains the - grader's response - - No ajax return is needed. Return empty dict. + Updates the current score via ajax. Called by xqueue. + Input: AJAX get dictionary, modulesystem + Output: None """ queuekey = get['queuekey'] score_msg = get['xqueue_body'] @@ -473,6 +527,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return dict() # No AJAX return is needed def get_html(self, system): + """ + Gets the HTML for this problem and renders it + Input: Modulesystem object + Output: Rendered HTML + """ #set context variables and render template if self.state != self.INITIAL: latest = self.latest_answer() From f858dce753565e4555e875d01ea8909ff4599668 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 19:12:30 -0500 Subject: [PATCH 140/171] Document self assessment --- .../lib/xmodule/xmodule/open_ended_module.py | 14 +++++-- common/lib/xmodule/xmodule/openendedchild.py | 41 ++----------------- .../xmodule/xmodule/self_assessment_module.py | 37 +++++++++++++---- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e4008309cd..ebd1cbfc02 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -39,6 +39,14 @@ log = logging.getLogger("mitx.courseware") class OpenEndedModule(openendedchild.OpenEndedChild): """ The open ended module supports all external open ended grader problems. + Sample XML file: + + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + """ def setup_response(self, system, location, definition, descriptor): """ @@ -562,7 +570,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ - Module for adding self assessment questions to courses + Module for adding open ended response questions to courses """ mako_template = "widgets/html-edit.html" module_class = OpenEndedModule @@ -578,12 +586,10 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): @classmethod def definition_from_xml(cls, xml_object, system): """ - Pull out the rubric, prompt, and submitmessage into a dictionary. + Pull out the open ended parameters into a dictionary. Returns: { - 'rubric': 'some-html', - 'prompt': 'some-html', 'oeparam': 'some-html' } """ diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 304271c620..5c2bdea76b 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -1,10 +1,3 @@ -""" -A Self Assessment module that allows students to write open-ended responses, -submit, then see a rubric and rate themselves. Persists student supplied -hints, answers, and assessment judgment (currently only correct/incorrect). -Parses xml definition file--see below for exact format. -""" - import copy from fs.errors import ResourceNotFoundError import itertools @@ -48,9 +41,9 @@ class OpenEndedChild(): initial (prompt, textbox shown) | - assessing (read-only textbox, rubric + assessment input shown) + assessing (read-only textbox, rubric + assessment input shown for self assessment, response queued for open ended) | - request_hint (read-only textbox, read-only rubric and assessment, hint input box shown) + post_assessment (read-only textbox, read-only rubric and assessment, hint input box shown) | done (submitted msg, green checkmark, everything else read-only. If attempts < max, shows a reset button that goes back to initial state. Saves previous @@ -69,6 +62,7 @@ class OpenEndedChild(): POST_ASSESSMENT = 'post_assessment' DONE = 'done' + #This is used to tell students where they are at in the module HUMAN_NAMES={ 'initial' : 'Started', 'assessing' : 'Being scored', @@ -78,35 +72,6 @@ class OpenEndedChild(): def __init__(self, system, location, definition, descriptor, static_data, instance_state=None, shared_state=None, **kwargs): - """ - Definition file should have 4 blocks -- prompt, rubric, submitmessage, hintprompt, - and two optional attributes: - attempts, which should be an integer that defaults to 1. - If it's > 1, the student will be able to re-submit after they see - the rubric. - max_score, which should be an integer that defaults to 1. - It defines the maximum number of points a student can get. Assumed to be integer scale - from 0 to max_score, with an interval of 1. - - Note: all the submissions are stored. - - Sample file: - - - - Insert prompt text here. (arbitrary html) - - - Insert grading rubric here. (arbitrary html) - - - Please enter a hint below: (arbitrary html) - - - Thanks for submitting! (arbitrary html) - - - """ # Load instance state if instance_state is not None: diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 52701a8cf1..88632a38d0 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -1,10 +1,3 @@ -""" -A Self Assessment module that allows students to write open-ended responses, -submit, then see a rubric and rate themselves. Persists student supplied -hints, answers, and assessment judgment (currently only correct/incorrect). -Parses xml definition file--see below for exact format. -""" - import copy from fs.errors import ResourceNotFoundError import itertools @@ -31,14 +24,42 @@ import openendedchild log = logging.getLogger("mitx.courseware") class SelfAssessmentModule(openendedchild.OpenEndedChild): + """ + A Self Assessment module that allows students to write open-ended responses, + submit, then see a rubric and rate themselves. Persists student supplied + hints, answers, and assessment judgment (currently only correct/incorrect). + Parses xml definition file--see below for exact format. + Sample XML format: + + + What hint about this problem would you give to someone? + + + Save Succcesful. Thanks for participating! + + + """ def setup_response(self, system, location, definition, descriptor): + """ + Sets up the module + @param system: Modulesystem + @param location: location, to let the module know where it is. + @param definition: XML definition of the module. + @param descriptor: SelfAssessmentDescriptor + @return: None + """ self.submit_message = definition['submitmessage'] self.hint_prompt = definition['hintprompt'] self.prompt = stringify_children(self.prompt) self.rubric = stringify_children(self.rubric) def get_html(self, system): + """ + Gets context and renders HTML that represents the module + @param system: Modulesystem + @return: Rendered HTML + """ #set context variables and render template if self.state != self.INITIAL: latest = self.latest_answer() @@ -266,8 +287,6 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): Returns: { - 'rubric': 'some-html', - 'prompt': 'some-html', 'submitmessage': 'some-html' 'hintprompt': 'some-html' } From 4a2875ccdd302fa1d08990ed02912dd2e2182c0e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 8 Jan 2013 19:15:57 -0500 Subject: [PATCH 141/171] Document open ended child --- common/lib/xmodule/xmodule/openendedchild.py | 31 +++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 5c2bdea76b..ce1b15074f 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -72,7 +72,6 @@ class OpenEndedChild(): def __init__(self, system, location, definition, descriptor, static_data, instance_state=None, shared_state=None, **kwargs): - # Load instance state if instance_state is not None: instance_state = json.loads(instance_state) @@ -102,6 +101,14 @@ class OpenEndedChild(): self.setup_response(system, location, definition, descriptor) def setup_response(self, system, location, definition, descriptor): + """ + Needs to be implemented by the inheritors of this module. Sets up additional fields used by the child modules. + @param system: Modulesystem + @param location: Module location + @param definition: XML definition + @param descriptor: Descriptor of the module + @return: None + """ pass def latest_answer(self): @@ -123,6 +130,11 @@ class OpenEndedChild(): return self.history[-1].get('post_assessment', "") def new_history_entry(self, answer): + """ + Adds a new entry to the history dictionary + @param answer: The student supplied answer + @return: None + """ self.history.append({'answer': answer}) def record_latest_score(self, score): @@ -213,12 +225,25 @@ class OpenEndedChild(): 'error': 'The problem state got out-of-sync'} def get_html(self): + """ + Needs to be implemented by inheritors. Renders the HTML that students see. + @return: + """ pass def handle_ajax(self): + """ + Needs to be implemented by child modules. Handles AJAX events. + @return: + """ pass def is_submission_correct(self, score): + """ + Checks to see if a given score makes the answer correct. Very naive right now (>66% is correct) + @param score: Numeric score. + @return: Boolean correct. + """ correct=False if(isinstance(score,(int, long, float, complex))): score_ratio = int(score) / float(self.max_score()) @@ -226,6 +251,10 @@ class OpenEndedChild(): return correct def is_last_response_correct(self): + """ + Checks to see if the last response in the module is correct. + @return: 'correct' if correct, otherwise 'incorrect' + """ score=self.get_score()['score'] correctness = 'correct' if self.is_submission_correct(score) else 'incorrect' return correctness From 5a3a537c1b1668eec294966d5d1ea15cdc43543a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 13:00:37 -0500 Subject: [PATCH 142/171] Support formatting of peer grading feedback --- .../lib/xmodule/xmodule/open_ended_module.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index ebd1cbfc02..0d30950592 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -422,8 +422,22 @@ class OpenEndedModule(openendedchild.OpenEndedChild): log.error("External grader message is missing required tag: {0}" .format(tag)) return fail - - feedback = self._format_feedback(score_result) + #This is to support peer grading + if isinstance(score_result['score'], list): + feedback_items=[] + for i in xrange(0,len(score_result['score'])): + new_score_result={ + 'score' : score_result['score'][i], + 'feedback' : score_result['feedback'][i], + 'grader_type' : score_result['grader_type'], + 'success' : score_result['success'], + 'grader_id' : score_result['grader_id'][i], + 'submission_id' : score_result['submission_id'] + } + feedback_items.append(self._format_feedback(new_score_result)) + feedback="".join(feedback_items) + else: + feedback = self._format_feedback(score_result) self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] From c267efb40099d6fc2e3508257caa21e94378b800 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 13:03:01 -0500 Subject: [PATCH 143/171] Add in a comment --- common/lib/xmodule/xmodule/open_ended_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 0d30950592..1ce1e09e03 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -437,6 +437,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): feedback_items.append(self._format_feedback(new_score_result)) feedback="".join(feedback_items) else: + #This is for instructor and ML grading feedback = self._format_feedback(score_result) self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] From c4b1c8d074bdb4efe1588acc474f26a2635e61c9 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 14:29:49 -0500 Subject: [PATCH 144/171] Correct peer grading score parsing --- common/lib/xmodule/xmodule/open_ended_module.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 1ce1e09e03..07ceb79b1a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -31,6 +31,7 @@ from capa.util import * import openendedchild from mitxmako.shortcuts import render_to_string +from numpy import median from datetime import datetime @@ -436,9 +437,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): } feedback_items.append(self._format_feedback(new_score_result)) feedback="".join(feedback_items) + score = median(score_result['score']) else: #This is for instructor and ML grading feedback = self._format_feedback(score_result) + self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] From 742d9475c79ae1faa7d6043e3597d70ece7cef11 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 14:31:02 -0500 Subject: [PATCH 145/171] Parse int from score --- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 07ceb79b1a..ac852e4e5e 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -437,7 +437,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): } feedback_items.append(self._format_feedback(new_score_result)) feedback="".join(feedback_items) - score = median(score_result['score']) + score = int(median(score_result['score'])) else: #This is for instructor and ML grading feedback = self._format_feedback(score_result) From 62e93870957c01e0e6b013103c2c06dee4bb6d4d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 14:42:29 -0500 Subject: [PATCH 146/171] Fix score passing from controller --- common/lib/xmodule/xmodule/open_ended_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index ac852e4e5e..2a253c663f 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -441,11 +441,12 @@ class OpenEndedModule(openendedchild.OpenEndedChild): else: #This is for instructor and ML grading feedback = self._format_feedback(score_result) + score=score_result['score'] self.submission_id=score_result['submission_id'] self.grader_id=score_result['grader_id'] - return {'valid' : True, 'score' : score_result['score'], 'feedback' : feedback} + return {'valid' : True, 'score' : score, 'feedback' : feedback} def latest_post_assessment(self, short_feedback=False): """ From cb203a6f5532ec052c668fd5969261385fce56cc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 15:54:15 -0500 Subject: [PATCH 147/171] Better error messages --- .../xmodule/combined_open_ended_module.py | 1 + .../lib/xmodule/xmodule/open_ended_module.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 4bc0c1fc85..42f2393ad9 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -224,6 +224,7 @@ class CombinedOpenEndedModule(XModule): current_task_state=self.overwrite_state(current_task_state) self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) + log.debug(current_task_state) return True def check_allow_reset(self): diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 2a253c663f..367b5d9e67 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -405,23 +405,29 @@ class OpenEndedModule(openendedchild.OpenEndedChild): correct: Correctness of submission (Boolean) score: Points to be assigned (numeric, can be float) """ - fail = {'valid' : False, 'correct' : False, 'points' : 0, 'msg' : ''} + fail = {'valid' : False, 'score' : 0, 'feedback' : ''} try: score_result = json.loads(score_msg) except (TypeError, ValueError): - log.error("External grader message should be a JSON-serialized dict." - " Received score_msg = {0}".format(score_msg)) + error_message=("External grader message should be a JSON-serialized dict." + " Received score_msg = {0}".format(score_msg)) + log.error(error_message) + fail['feedback']=error_message return fail if not isinstance(score_result, dict): - log.error("External grader message should be a JSON-serialized dict." - " Received score_result = {0}".format(score_result)) + error_message=("External grader message should be a JSON-serialized dict." + " Received score_result = {0}".format(score_result)) + log.error(error_message) + fail['feedback']=error_message return fail for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']: if tag not in score_result: - log.error("External grader message is missing required tag: {0}" + error_message=("External grader message is missing required tag: {0}" .format(tag)) + log.error(error_message) + fail['feedback']=error _message return fail #This is to support peer grading if isinstance(score_result['score'], list): From f4968e1e8e1b80677130a662b5adb5c85c6a7f44 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 16:01:03 -0500 Subject: [PATCH 148/171] Fix spacing error --- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 367b5d9e67..0420faf534 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -427,7 +427,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): error_message=("External grader message is missing required tag: {0}" .format(tag)) log.error(error_message) - fail['feedback']=error _message + fail['feedback']=error_message return fail #This is to support peer grading if isinstance(score_result['score'], list): From b0d3bcc5166feced1285826f745e42c8bd5ed871 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 16:37:15 -0500 Subject: [PATCH 149/171] Fix feedback response for peer grading so that students can respond to multiple feedback items --- .../capa/capa/templates/openendedinput.html | 56 ----------------- .../xmodule/combined_open_ended_module.py | 10 ++- .../js/src/combinedopenended/display.coffee | 62 ++++++++++--------- .../lib/xmodule/xmodule/open_ended_module.py | 11 ++-- 4 files changed, 48 insertions(+), 91 deletions(-) delete mode 100644 common/lib/capa/capa/templates/openendedinput.html diff --git a/common/lib/capa/capa/templates/openendedinput.html b/common/lib/capa/capa/templates/openendedinput.html deleted file mode 100644 index c42ad73faf..0000000000 --- a/common/lib/capa/capa/templates/openendedinput.html +++ /dev/null @@ -1,56 +0,0 @@ -
- - -
- % if status == 'unsubmitted': - Unanswered - % elif status == 'correct': - Correct - % elif status == 'incorrect': - Incorrect - % elif status == 'queued': - Submitted for grading - % endif - - % if hidden: -
- % endif -
- - - - % if status == 'queued': - - % endif -
- ${msg|n} - % if status in ['correct','incorrect']: -
-
- Respond to Feedback -
-
-

How accurate do you find this feedback?

-
-
    -
  • -
  • -
  • -
  • -
  • -
-
-

Additional comments:

- -
- -
-
-
- % endif -
-
diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 42f2393ad9..244346625a 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -336,8 +336,14 @@ class CombinedOpenEndedModule(XModule): last_post_assessment = task.latest_post_assessment() last_post_feedback="" if task_type=="openended": - last_post_assessment = task.latest_post_assessment(short_feedback=False) - last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) + last_post_assessment = task.latest_post_assessment(short_feedback=False, join_feedback=False) + if isinstance(last_post_assessment,list): + eval_list=[] + for i in xrange(0,len(last_post_assessment)): + eval_list.append(task.format_feedback_with_evaluation(last_post_assessment[i])) + last_post_evaluation="".join(eval_list) + else: + last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) last_post_assessment = last_post_evaluation last_correctness = task.is_last_response_correct() max_score = task.max_score() diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 8a5ef42270..5e2d2db86e 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -69,6 +69,39 @@ class @CombinedOpenEnded else @errors_area.html(response.error) + message_post: (event)=> + Logger.log 'message_post', @answers + external_grader_message=$(event.target).parent().parent().parent() + evaluation_scoring = $(event.target).parent() + + fd = new FormData() + feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value + submission_id = external_grader_message.find('div.submission_id')[0].innerHTML + grader_id = external_grader_message.find('div.grader_id')[0].innerHTML + score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val() + + fd.append('feedback', feedback) + fd.append('submission_id', submission_id) + fd.append('grader_id', grader_id) + if(!score) + @gentle_alert "You need to pick a rating before you can submit." + return + else + fd.append('score', score) + + settings = + type: "POST" + data: fd + processData: false + contentType: false + success: (response) => + @gentle_alert response.msg + $('section.evaluation').slideToggle() + @message_wrapper.html(response.message_html) + + $.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings) + + rebind: () => # rebind to the appropriate function for the current state @submit_button.unbind('click') @@ -227,35 +260,6 @@ class @CombinedOpenEnded else @errors_area.html('Problem state got out of sync. Try reloading the page.') - message_post: => - Logger.log 'message_post', @answers - - fd = new FormData() - feedback = $('section.evaluation textarea.feedback-on-feedback')[0].value - submission_id = $('div.external-grader-message div.submission_id')[0].innerHTML - grader_id = $('div.external-grader-message div.grader_id')[0].innerHTML - score = $(".evaluation-scoring input:radio[name='evaluation-score']:checked").val() - fd.append('feedback', feedback) - fd.append('submission_id', submission_id) - fd.append('grader_id', grader_id) - if(!score) - @gentle_alert "You need to pick a rating before you can submit." - return - else - fd.append('score', score) - - settings = - type: "POST" - data: fd - processData: false - contentType: false - success: (response) => - @gentle_alert response.msg - $('section.evaluation').slideToggle() - @message_wrapper.html(response.message_html) - - $.ajaxWithPrefix("#{@ajax_url}/save_post_assessment", settings) - gentle_alert: (msg) => if @el.find('.open-ended-alert').length @el.find('.open-ended-alert').remove() diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 0420faf534..f715c9d76a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -391,7 +391,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return feedback_template - def _parse_score_msg(self, score_msg): + def _parse_score_msg(self, score_msg, join_feedback=True): """ Grader reply is a JSON-dump of the following dict { 'correct': True/False, @@ -442,7 +442,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'submission_id' : score_result['submission_id'] } feedback_items.append(self._format_feedback(new_score_result)) - feedback="".join(feedback_items) + if join_feedback: + feedback="".join(feedback_items) + else: + feedback=feedback_items score = int(median(score_result['score'])) else: #This is for instructor and ML grading @@ -454,7 +457,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): return {'valid' : True, 'score' : score, 'feedback' : feedback} - def latest_post_assessment(self, short_feedback=False): + def latest_post_assessment(self, short_feedback=False, join_feedback=True): """ Gets the latest feedback, parses, and returns @param short_feedback: If the long feedback is wanted or not @@ -463,7 +466,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if not self.history: return "" - feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', "")) + feedback_dict = self._parse_score_msg(self.history[-1].get('post_assessment', ""), join_feedback=join_feedback) if not short_feedback: return feedback_dict['feedback'] if feedback_dict['valid'] else '' if feedback_dict['valid']: From e4568c3a2061b7ba17a6a1af45fac9f87e955762 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 16:41:17 -0500 Subject: [PATCH 150/171] Use include clearfix in css --- .../xmodule/xmodule/css/combinedopenended/display.scss | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index be86757aee..8ebb3a2888 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -21,6 +21,7 @@ h2 { } section.combined-open-ended { + @include clearfix; .status-container { float:right; @@ -40,14 +41,6 @@ section.combined-open-ended { position:relative; } - &:after - { - content:"."; - display:block; - height:0; - visibility: hidden; - clear:both; - } } section.combined-open-ended-status { From ee2990da4f0c8151a00f2fb7ed401c44344d860c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 17:05:08 -0500 Subject: [PATCH 151/171] Change hidden div to input name --- common/lib/xmodule/xmodule/css/combinedopenended/display.scss | 1 - .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++-- common/lib/xmodule/xmodule/open_ended_module.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 8ebb3a2888..b5eb4b52e6 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -40,7 +40,6 @@ section.combined-open-ended { width: 93%; position:relative; } - } section.combined-open-ended-status { diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 5e2d2db86e..29b0424a6d 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -76,8 +76,8 @@ class @CombinedOpenEnded fd = new FormData() feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value - submission_id = external_grader_message.find('div.submission_id')[0].innerHTML - grader_id = external_grader_message.find('div.grader_id')[0].innerHTML + submission_id = external_grader_message.find('input.submission_id')[0].innerHTML + grader_id = external_grader_message.find('input.grader_id')[0].innerHTML score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val() fd.append('feedback', feedback) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index f715c9d76a..3f5d5b0110 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -332,9 +332,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def format_feedback_hidden(feedback_type , value): feedback_type,value=encode_values(feedback_type,value) feedback = """ - + """.format(feedback_type=feedback_type, value=value) return feedback From c76786ae4ee6ad63a88ed875e601884c24dcebdd Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 17:08:10 -0500 Subject: [PATCH 152/171] Roll back hidden input changes --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++-- common/lib/xmodule/xmodule/open_ended_module.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 29b0424a6d..5e2d2db86e 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -76,8 +76,8 @@ class @CombinedOpenEnded fd = new FormData() feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value - submission_id = external_grader_message.find('input.submission_id')[0].innerHTML - grader_id = external_grader_message.find('input.grader_id')[0].innerHTML + submission_id = external_grader_message.find('div.submission_id')[0].innerHTML + grader_id = external_grader_message.find('div.grader_id')[0].innerHTML score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val() fd.append('feedback', feedback) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 3f5d5b0110..f715c9d76a 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -332,9 +332,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def format_feedback_hidden(feedback_type , value): feedback_type,value=encode_values(feedback_type,value) feedback = """ - + """.format(feedback_type=feedback_type, value=value) return feedback From 8dbbb021a730256ef0ba3a9f04d58085d78ad9f2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 9 Jan 2013 17:23:33 -0500 Subject: [PATCH 153/171] Change value passing to hidden input type --- .../xmodule/xmodule/js/src/combinedopenended/display.coffee | 4 ++-- common/lib/xmodule/xmodule/open_ended_module.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee index 5e2d2db86e..2cbba143a3 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee @@ -76,8 +76,8 @@ class @CombinedOpenEnded fd = new FormData() feedback = evaluation_scoring.find('textarea.feedback-on-feedback')[0].value - submission_id = external_grader_message.find('div.submission_id')[0].innerHTML - grader_id = external_grader_message.find('div.grader_id')[0].innerHTML + submission_id = external_grader_message.find('input.submission_id')[0].value + grader_id = external_grader_message.find('input.grader_id')[0].value score = evaluation_scoring.find("input:radio[name='evaluation-score']:checked").val() fd.append('feedback', feedback) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index f715c9d76a..5649cbbd2c 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -332,9 +332,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def format_feedback_hidden(feedback_type , value): feedback_type,value=encode_values(feedback_type,value) feedback = """ - + """.format(feedback_type=feedback_type, value=value) return feedback From bcbf65e2b9f1108452f68136d802796463b1f974 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 09:45:10 -0500 Subject: [PATCH 154/171] Code reformat, line length fix, change text names to variables --- .../xmodule/combined_open_ended_module.py | 282 +++++++++--------- .../lib/xmodule/xmodule/open_ended_module.py | 214 ++++++------- common/lib/xmodule/xmodule/openendedchild.py | 22 +- .../xmodule/xmodule/self_assessment_module.py | 18 +- 4 files changed, 275 insertions(+), 261 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 244346625a..358a3b6995 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -45,14 +45,14 @@ class CombinedOpenEndedModule(XModule): # states INITIAL = 'initial' ASSESSING = 'assessing' - INTERMEDIATE_DONE='intermediate_done' + INTERMEDIATE_DONE = 'intermediate_done' DONE = 'done' - TASK_TYPES=["self", "ml", "instructor", "peer"] + TASK_TYPES = ["self", "ml", "instructor", "peer"] js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/javascript_loader.coffee'), - ]} + ]} js_module_name = "CombinedOpenEnded" css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]} @@ -88,7 +88,8 @@ class CombinedOpenEndedModule(XModule): Enter essay here. This is the answer. - {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + {"grader_settings" : "ml_grading.conf", + "problem_id" : "6.002x/Welcome/OETest"}
@@ -108,9 +109,9 @@ class CombinedOpenEndedModule(XModule): #Tells the system which xml definition to load self.current_task_number = instance_state.get('current_task_number', 0) #This loads the states of the individual children - self.task_states= instance_state.get('task_states', []) + self.task_states = instance_state.get('task_states', []) #Overall state of the combined open ended module - self.state = instance_state.get('state', 'initial') + self.state = instance_state.get('state', self.INITIAL) self.attempts = instance_state.get('attempts', 0) @@ -124,13 +125,13 @@ class CombinedOpenEndedModule(XModule): #Static data is passed to the child modules to render self.static_data = { - 'max_score' : self._max_score, - 'max_attempts' : self.max_attempts, - 'prompt' : definition['prompt'], - 'rubric' : definition['rubric'] + 'max_score': self._max_score, + 'max_attempts': self.max_attempts, + 'prompt': definition['prompt'], + 'rubric': definition['rubric'] } - self.task_xml=definition['task_xml'] + self.task_xml = definition['task_xml'] self.setup_next_task() def get_tag_name(self, xml): @@ -139,7 +140,7 @@ class CombinedOpenEndedModule(XModule): Input: XML string Output: The name of the root tag """ - tag=etree.fromstring(xml).tag + tag = etree.fromstring(xml).tag return tag def overwrite_state(self, current_task_state): @@ -149,15 +150,15 @@ class CombinedOpenEndedModule(XModule): Input: Task state json string Output: Task state json string """ - 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'] - loaded_task_state=json.loads(current_task_state) - if loaded_task_state['state']== self.INITIAL: - loaded_task_state['state']=self.ASSESSING + 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}) - current_task_state=json.dumps(loaded_task_state) + loaded_task_state['history'].append({'answer': last_response}) + current_task_state = json.dumps(loaded_task_state) return current_task_state def child_modules(self): @@ -167,17 +168,17 @@ class CombinedOpenEndedModule(XModule): Input: None Output: A dictionary of dictionaries containing the descriptor functions and module functions """ - child_modules={ - 'openended' : open_ended_module.OpenEndedModule, - 'selfassessment' : self_assessment_module.SelfAssessmentModule, + child_modules = { + 'openended': open_ended_module.OpenEndedModule, + 'selfassessment': self_assessment_module.SelfAssessmentModule, } - child_descriptors={ - 'openended' : open_ended_module.OpenEndedDescriptor, - 'selfassessment' : self_assessment_module.SelfAssessmentDescriptor, + child_descriptors = { + 'openended': open_ended_module.OpenEndedDescriptor, + 'selfassessment': self_assessment_module.SelfAssessmentDescriptor, } - children={ - 'modules' : child_modules, - 'descriptors' : child_descriptors, + children = { + 'modules': child_modules, + 'descriptors': child_descriptors, } return children @@ -188,41 +189,47 @@ class CombinedOpenEndedModule(XModule): Input: A boolean indicating whether or not the reset function is calling. Output: Boolean True (not useful right now) """ - current_task_state=None - if len(self.task_states)>self.current_task_number: - current_task_state=self.task_states[self.current_task_number] + current_task_state = None + if len(self.task_states) > self.current_task_number: + current_task_state = self.task_states[self.current_task_number] - 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: - self.allow_reset=self.check_allow_reset() + if self.current_task_number > 0: + self.allow_reset = self.check_allow_reset() if self.allow_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) - children=self.child_modules() + children = self.child_modules() - self.current_task_descriptor=children['descriptors'][current_task_type](self.system) - etree_xml=etree.fromstring(self.current_task_xml) + self.current_task_descriptor = children['descriptors'][current_task_type](self.system) + etree_xml = etree.fromstring(self.current_task_xml) - 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=children['modules'][current_task_type](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.definition_from_xml(etree_xml, self.system) + if current_task_state is None and self.current_task_number == 0: + self.current_task = children['modules'][current_task_type](self.system, self.location, + self.current_task_parsed_xml, self.current_task_descriptor, self.static_data) self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING - elif current_task_state is None and self.current_task_number>0: - last_response_data =self.get_last_response(self.current_task_number-1) + self.state = self.ASSESSING + elif current_task_state is None and self.current_task_number > 0: + last_response_data = self.get_last_response(self.current_task_number - 1) last_response = last_response_data['response'] - current_task_state = ('{"state": "assessing", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) + current_task_state = ( + '{"state": "' + str(self.ASSESSING) + '", "version": 1, "max_score": ' + str(self._max_score) + ', ' + + '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') + self.current_task = children['modules'][current_task_type](self.system, self.location, + self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, + instance_state=current_task_state) self.task_states.append(self.current_task.get_instance_state()) - self.state=self.ASSESSING + self.state = self.ASSESSING else: - if self.current_task_number>0 and not reset: - current_task_state=self.overwrite_state(current_task_state) - self.current_task=children['modules'][current_task_type](self.system, self.location, self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, instance_state=current_task_state) + if self.current_task_number > 0 and not reset: + current_task_state = self.overwrite_state(current_task_state) + self.current_task = children['modules'][current_task_type](self.system, self.location, + self.current_task_parsed_xml, self.current_task_descriptor, self.static_data, + instance_state=current_task_state) log.debug(current_task_state) return True @@ -235,13 +242,14 @@ class CombinedOpenEndedModule(XModule): Output: the allow_reset attribute of the current module. """ if not self.allow_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) + 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) - 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.allow_reset = True return self.allow_reset @@ -251,18 +259,18 @@ class CombinedOpenEndedModule(XModule): Input: None Output: A dictionary that can be rendered into the combined open ended template. """ - task_html=self.get_html_base() + task_html = self.get_html_base() #set context variables and render template context = { - 'items': [{'content' : task_html}], + 'items': [{'content': task_html}], 'ajax_url': self.system.ajax_url, 'allow_reset': self.allow_reset, - 'state' : self.state, - 'task_count' : len(self.task_xml), - 'task_number' : self.current_task_number+1, - 'status' : self.get_status(), - } + 'state': self.state, + 'task_count': len(self.task_xml), + 'task_number': self.current_task_number + 1, + 'status': self.get_status(), + } return context @@ -272,7 +280,7 @@ class CombinedOpenEndedModule(XModule): Input: None Output: rendered html """ - context=self.get_context() + context = self.get_context() html = self.system.render_template('combined_open_ended.html', context) return html @@ -283,7 +291,7 @@ class CombinedOpenEndedModule(XModule): Input: None Output: HTML rendered directly via Mako """ - context=self.get_context() + context = self.get_context() html = render_to_string('combined_open_ended.html', context) return html @@ -304,11 +312,11 @@ class CombinedOpenEndedModule(XModule): Input: The number of the task. Output: The minimum and maximum scores needed to move on to the specified task. """ - task_xml=self.task_xml[task_number] - etree_xml=etree.fromstring(task_xml) - min_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',0)) - max_score_to_attempt=int(etree_xml.attrib.get('max_score_to_attempt',self._max_score)) - return {'min_score_to_attempt' : min_score_to_attempt, 'max_score_to_attempt' : max_score_to_attempt} + task_xml = self.task_xml[task_number] + etree_xml = etree.fromstring(task_xml) + min_score_to_attempt = int(etree_xml.attrib.get('min_score_to_attempt', 0)) + max_score_to_attempt = int(etree_xml.attrib.get('max_score_to_attempt', self._max_score)) + return {'min_score_to_attempt': min_score_to_attempt, 'max_score_to_attempt': max_score_to_attempt} def get_last_response(self, task_number): """ @@ -316,49 +324,50 @@ class CombinedOpenEndedModule(XModule): Input: The number of the task. Output: A dictionary that contains information about the specified task. """ - last_response="" + last_response = "" task_state = self.task_states[task_number] - task_xml=self.task_xml[task_number] - task_type=self.get_tag_name(task_xml) + task_xml = self.task_xml[task_number] + task_type = self.get_tag_name(task_xml) - children=self.child_modules() + children = self.child_modules() - task_descriptor=children['descriptors'][task_type](self.system) - etree_xml=etree.fromstring(task_xml) + task_descriptor = children['descriptors'][task_type](self.system) + etree_xml = etree.fromstring(task_xml) - min_score_to_attempt=int(etree_xml.attrib.get('min_score_to_attempt',0)) - max_score_to_attempt=int(etree_xml.attrib.get('max_score_to_attempt',self._max_score)) + min_score_to_attempt = int(etree_xml.attrib.get('min_score_to_attempt', 0)) + max_score_to_attempt = int(etree_xml.attrib.get('max_score_to_attempt', self._max_score)) - 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) - last_response=task.latest_answer() + 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) + last_response = task.latest_answer() last_score = task.latest_score() last_post_assessment = task.latest_post_assessment() - last_post_feedback="" - if task_type=="openended": + last_post_feedback = "" + if task_type == "openended": last_post_assessment = task.latest_post_assessment(short_feedback=False, join_feedback=False) - if isinstance(last_post_assessment,list): - eval_list=[] - for i in xrange(0,len(last_post_assessment)): + if isinstance(last_post_assessment, list): + eval_list = [] + for i in xrange(0, len(last_post_assessment)): eval_list.append(task.format_feedback_with_evaluation(last_post_assessment[i])) - last_post_evaluation="".join(eval_list) + last_post_evaluation = "".join(eval_list) else: last_post_evaluation = task.format_feedback_with_evaluation(last_post_assessment) last_post_assessment = last_post_evaluation last_correctness = task.is_last_response_correct() max_score = task.max_score() state = task.state - last_response_dict={ - 'response' : last_response, - 'score' : last_score, - 'post_assessment' : last_post_assessment, - 'type' : task_type, - 'max_score' : max_score, - 'state' : state, - 'human_state' : task.HUMAN_NAMES[state], - 'correct' : last_correctness, - 'min_score_to_attempt' : min_score_to_attempt, - 'max_score_to_attempt' : max_score_to_attempt, + last_response_dict = { + 'response': last_response, + 'score': last_score, + 'post_assessment': last_post_assessment, + 'type': task_type, + 'max_score': max_score, + 'state': state, + 'human_state': task.HUMAN_NAMES[state], + 'correct': last_correctness, + 'min_score_to_attempt': min_score_to_attempt, + 'max_score_to_attempt': max_score_to_attempt, } return last_response_dict @@ -369,28 +378,28 @@ class CombinedOpenEndedModule(XModule): Input: None Output: boolean indicating whether or not the task state changed. """ - changed=False + changed = False 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: - self.current_task_number+=1 - if self.current_task_number>=(len(self.task_xml)): - self.state=self.DONE - self.current_task_number=len(self.task_xml)-1 + current_task_state = json.loads(self.task_states[self.current_task_number]) + if current_task_state['state'] == self.DONE: + self.current_task_number += 1 + if self.current_task_number >= (len(self.task_xml)): + self.state = self.DONE + self.current_task_number = len(self.task_xml) - 1 else: - self.state=self.INITIAL - changed=True + self.state = self.INITIAL + changed = True self.setup_next_task() return changed - def update_task_states_ajax(self,return_html): + def update_task_states_ajax(self, return_html): """ Runs the update task states function for ajax calls. Currently the same as update_task_states Input: The html returned by the handle_ajax function of the child Output: New html that should be rendered """ - changed=self.update_task_states() + changed = self.update_task_states() if changed: #return_html=self.get_html() pass @@ -402,12 +411,12 @@ class CombinedOpenEndedModule(XModule): Input: AJAX get dictionary Output: Dictionary to be rendered via ajax that contains the result html. """ - task_number=int(get['task_number']) + task_number = int(get['task_number']) self.update_task_states() - response_dict=self.get_last_response(task_number) - context = {'results' : response_dict['post_assessment'], 'task_number' : task_number+1} + response_dict = self.get_last_response(task_number) + context = {'results': response_dict['post_assessment'], 'task_number': task_number + 1} html = render_to_string('combined_open_ended_results.html', context) - return {'html' : html, 'success' : True} + return {'html': html, 'success': True} def handle_ajax(self, dispatch, get): """ @@ -423,15 +432,15 @@ class CombinedOpenEndedModule(XModule): handlers = { 'next_problem': self.next_problem, 'reset': self.reset, - 'get_results' : self.get_results - } + 'get_results': self.get_results + } if dispatch not in handlers: - return_html = self.current_task.handle_ajax(dispatch,get, self.system) + return_html = self.current_task.handle_ajax(dispatch, get, self.system) return self.update_task_states_ajax(return_html) d = handlers[dispatch](get) - return json.dumps(d,cls=ComplexEncoder) + return json.dumps(d, cls=ComplexEncoder) def next_problem(self, get): """ @@ -440,7 +449,7 @@ class CombinedOpenEndedModule(XModule): 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.allow_reset} def reset(self, get): """ @@ -457,17 +466,17 @@ class CombinedOpenEndedModule(XModule): 'success': False, 'error': 'Too many attempts.' } - self.state=self.INITIAL - self.allow_reset=False - for i in xrange(0,len(self.task_xml)): - self.current_task_number=i + self.state = self.INITIAL + self.allow_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.task_states[self.current_task_number] = self.current_task.get_instance_state() + self.current_task_number = 0 + self.allow_reset = False self.setup_next_task() - return {'success': True, 'html' : self.get_html_nonsystem()} + return {'success': True, 'html': self.get_html_nonsystem()} def get_instance_state(self): """ @@ -482,8 +491,8 @@ class CombinedOpenEndedModule(XModule): 'state': self.state, 'task_states': self.task_states, 'attempts': self.attempts, - 'ready_to_reset' : self.allow_reset, - } + 'ready_to_reset': self.allow_reset, + } return json.dumps(state) @@ -493,16 +502,17 @@ class CombinedOpenEndedModule(XModule): Input: None Output: The status html to be rendered """ - status=[] - for i in xrange(0,self.current_task_number+1): + status = [] + for i in xrange(0, self.current_task_number + 1): task_data = self.get_last_response(i) - task_data.update({'task_number' : i+1}) + task_data.update({'task_number': i + 1}) status.append(task_data) - context = {'status_list' : status} + context = {'status_list': status} status_html = self.system.render_template("combined_open_ended_status.html", context) return status_html + class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding combined open ended questions @@ -532,18 +542,18 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ expected_children = ['task', 'rubric', 'prompt'] for child in expected_children: - if len(xml_object.xpath(child)) == 0 : + if len(xml_object.xpath(child)) == 0: raise ValueError("Combined Open Ended definition must include at least one '{0}' tag".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)))] + 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 {'task_xml': parse_task('task'), 'prompt' : parse('prompt'), 'rubric' : parse('rubric')} + return {'task_xml': parse_task('task'), 'prompt': parse('prompt'), 'rubric': parse('rubric')} def definition_to_xml(self, resource_fs): diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 5649cbbd2c..45d6501816 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -49,6 +49,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): """ + def setup_response(self, system, location, definition, descriptor): """ Sets up the response type. @@ -65,8 +66,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.message_queue_name = definition.get('message-queuename', self.DEFAULT_MESSAGE_QUEUE) #This is needed to attach feedback to specific responses later - self.submission_id=None - self.grader_id=None + self.submission_id = None + self.grader_id = None if oeparam is None: raise ValueError("No oeparam found in problem xml.") @@ -77,10 +78,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self._parse(oeparam, self.prompt, self.rubric, system) - if self.created=="True" and self.state == self.ASSESSING: - self.created="False" + if self.created == "True" and self.state == self.ASSESSING: + self.created = "False" self.send_to_grader(self.latest_answer(), system) - self.created="False" + self.created = "False" def _parse(self, oeparam, prompt, rubric, system): ''' @@ -94,8 +95,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.prompt = prompt_string + self.rubric = rubric_string grader_payload = oeparam.find('grader_payload') grader_payload = grader_payload.text if grader_payload is not None else '' @@ -113,13 +114,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.answer = find_with_default(oeparam, 'answer_display', 'No answer given.') parsed_grader_payload.update({ - 'location' : system.location.url(), - 'course_id' : system.course_id, - 'prompt' : prompt_string, - 'rubric' : rubric_string, - 'initial_display' : self.initial_display, - 'answer' : self.answer, - }) + 'location': system.location.url(), + 'course_id': system.course_id, + 'prompt': prompt_string, + 'rubric': rubric_string, + 'initial_display': self.initial_display, + 'answer': self.answer, + }) updated_grader_payload = json.dumps(parsed_grader_payload) self.payload = {'grader_payload': updated_grader_payload} @@ -131,10 +132,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): @param system: ModuleSystem @return: Success indicator """ - self.state=self.DONE - return {'success' : True} + self.state = self.DONE + return {'success': True} - def message_post(self,get, system): + def message_post(self, get, system): """ Handles a student message post (a reaction to the grade they received from an open ended grader type) Returns a boolean success/fail and an error message @@ -143,22 +144,23 @@ class OpenEndedModule(openendedchild.OpenEndedChild): event_info = dict() event_info['problem_id'] = system.location.url() event_info['student_id'] = system.anonymous_student_id - event_info['survey_responses']= get + event_info['survey_responses'] = get - survey_responses=event_info['survey_responses'] + survey_responses = event_info['survey_responses'] for tag in ['feedback', 'submission_id', 'grader_id', 'score']: if tag not in survey_responses: - return {'success' : False, 'msg' : "Could not find needed tag {0}".format(tag)} + return {'success': False, 'msg': "Could not find needed tag {0}".format(tag)} try: - submission_id=int(survey_responses['submission_id']) + submission_id = int(survey_responses['submission_id']) grader_id = int(survey_responses['grader_id']) feedback = str(survey_responses['feedback'].encode('ascii', 'ignore')) score = int(survey_responses['score']) except: - error_message=("Could not parse submission id, grader id, " - "or feedback from message_post ajax call. Here is the message data: {0}".format(survey_responses)) + error_message = ("Could not parse submission id, grader id, " + "or feedback from message_post ajax call. Here is the message data: {0}".format( + survey_responses)) log.exception(error_message) - return {'success' : False, 'msg' : "There was an error saving your feedback. Please contact course staff."} + return {'success': False, 'msg': "There was an error saving your feedback. Please contact course staff."} qinterface = system.xqueue['interface'] qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) @@ -175,26 +177,26 @@ class OpenEndedModule(openendedchild.OpenEndedChild): student_info = {'anonymous_student_id': anonymous_student_id, 'submission_time': qtime, - } - contents= { - 'feedback' : feedback, - 'submission_id' : submission_id, - 'grader_id' : grader_id, + } + contents = { + 'feedback': feedback, + 'submission_id': submission_id, + 'grader_id': grader_id, 'score': score, - 'student_info' : json.dumps(student_info), - } + 'student_info': json.dumps(student_info), + } (error, msg) = qinterface.send_to_queue(header=xheader, body=json.dumps(contents)) #Convert error to a success value - success=True + success = True if error: - success=False + success = False - self.state=self.DONE + self.state = self.DONE - return {'success' : success, 'msg' : "Successfully submitted your feedback."} + return {'success': success, 'msg': "Successfully submitted your feedback."} def send_to_grader(self, submission, system): """ @@ -226,14 +228,14 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # Metadata related to the student submission revealed to the external grader student_info = {'anonymous_student_id': anonymous_student_id, 'submission_time': qtime, - } + } #Update contents with student response and student info contents.update({ 'student_info': json.dumps(student_info), 'student_response': submission, - 'max_score' : self.max_score(), - }) + 'max_score': self.max_score(), + }) # Submit request. When successful, 'msg' is the prior length of the queue (error, msg) = qinterface.send_to_queue(header=xheader, @@ -241,7 +243,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): # State associated with the queueing request queuestate = {'key': queuekey, - 'time': qtime,} + 'time': qtime, } return True def _update_score(self, score_msg, queuekey, system): @@ -258,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.state = self.POST_ASSESSMENT return True @@ -313,24 +315,24 @@ class OpenEndedModule(openendedchild.OpenEndedChild): """ return priorities.get(elt[0], default_priority) - def encode_values(feedback_type,value): - feedback_type=str(feedback_type).encode('ascii', 'ignore') - if not isinstance(value,basestring): - value=str(value) - value=value.encode('ascii', 'ignore') - return feedback_type,value + def encode_values(feedback_type, value): + feedback_type = str(feedback_type).encode('ascii', 'ignore') + if not isinstance(value, basestring): + value = str(value) + value = value.encode('ascii', 'ignore') + return feedback_type, value def format_feedback(feedback_type, value): - feedback_type,value=encode_values(feedback_type,value) - feedback= """ + feedback_type, value = encode_values(feedback_type, value) + feedback = """
{value}
""".format(feedback_type=feedback_type, value=value) return feedback - def format_feedback_hidden(feedback_type , value): - feedback_type,value=encode_values(feedback_type,value) + def format_feedback_hidden(feedback_type, value): + feedback_type, value = encode_values(feedback_type, value) feedback = """ """.format(feedback_type=feedback_type, value=value) @@ -360,11 +362,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): else: feedback_list_part1 = format_feedback('errors', response_items['feedback']) - feedback_list_part2=(u"\n".join([format_feedback_hidden(feedback_type,value) - for feedback_type,value in response_items.items() - if feedback_type in ['submission_id', 'grader_id']])) + feedback_list_part2 = (u"\n".join([format_feedback_hidden(feedback_type, value) + for feedback_type, value in response_items.items() + if feedback_type in ['submission_id', 'grader_id']])) - return u"\n".join([feedback_list_part1,feedback_list_part2]) + return u"\n".join([feedback_list_part1, feedback_list_part2]) def _format_feedback(self, response_items): """ @@ -378,13 +380,13 @@ class OpenEndedModule(openendedchild.OpenEndedChild): if not response_items['success']: return system.render_template("open_ended_error.html", - {'errors' : feedback}) + {'errors': feedback}) feedback_template = render_to_string("open_ended_feedback.html", { 'grader_type': response_items['grader_type'], 'score': "{0} / {1}".format(response_items['score'], self.max_score()), 'feedback': feedback, - }) + }) return feedback_template @@ -403,57 +405,57 @@ class OpenEndedModule(openendedchild.OpenEndedChild): correct: Correctness of submission (Boolean) score: Points to be assigned (numeric, can be float) """ - fail = {'valid' : False, 'score' : 0, 'feedback' : ''} + fail = {'valid': False, 'score': 0, 'feedback': ''} try: score_result = json.loads(score_msg) except (TypeError, ValueError): - error_message=("External grader message should be a JSON-serialized dict." - " Received score_msg = {0}".format(score_msg)) + error_message = ("External grader message should be a JSON-serialized dict." + " Received score_msg = {0}".format(score_msg)) log.error(error_message) - fail['feedback']=error_message + fail['feedback'] = error_message return fail if not isinstance(score_result, dict): - error_message=("External grader message should be a JSON-serialized dict." - " Received score_result = {0}".format(score_result)) + error_message = ("External grader message should be a JSON-serialized dict." + " Received score_result = {0}".format(score_result)) log.error(error_message) - fail['feedback']=error_message + fail['feedback'] = error_message return fail for tag in ['score', 'feedback', 'grader_type', 'success', 'grader_id', 'submission_id']: if tag not in score_result: - error_message=("External grader message is missing required tag: {0}" - .format(tag)) + error_message = ("External grader message is missing required tag: {0}" + .format(tag)) log.error(error_message) - fail['feedback']=error_message + fail['feedback'] = error_message return fail - #This is to support peer grading + #This is to support peer grading if isinstance(score_result['score'], list): - feedback_items=[] - for i in xrange(0,len(score_result['score'])): - new_score_result={ - 'score' : score_result['score'][i], - 'feedback' : score_result['feedback'][i], - 'grader_type' : score_result['grader_type'], - 'success' : score_result['success'], - 'grader_id' : score_result['grader_id'][i], - 'submission_id' : score_result['submission_id'] - } + feedback_items = [] + for i in xrange(0, len(score_result['score'])): + new_score_result = { + 'score': score_result['score'][i], + 'feedback': score_result['feedback'][i], + 'grader_type': score_result['grader_type'], + 'success': score_result['success'], + 'grader_id': score_result['grader_id'][i], + 'submission_id': score_result['submission_id'] + } feedback_items.append(self._format_feedback(new_score_result)) if join_feedback: - feedback="".join(feedback_items) + feedback = "".join(feedback_items) else: - feedback=feedback_items + feedback = feedback_items score = int(median(score_result['score'])) else: #This is for instructor and ML grading feedback = self._format_feedback(score_result) - score=score_result['score'] + score = score_result['score'] - self.submission_id=score_result['submission_id'] - self.grader_id=score_result['grader_id'] + self.submission_id = score_result['submission_id'] + self.grader_id = score_result['grader_id'] - return {'valid' : True, 'score' : score, 'feedback' : feedback} + return {'valid': True, 'score': score, 'feedback': feedback} def latest_post_assessment(self, short_feedback=False, join_feedback=True): """ @@ -468,17 +470,18 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 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', ""))) + short_feedback = self._convert_longform_feedback_to_html( + json.loads(self.history[-1].get('post_assessment', ""))) return short_feedback if feedback_dict['valid'] else '' - def format_feedback_with_evaluation(self,feedback): + def format_feedback_with_evaluation(self, feedback): """ Renders a given html feedback into an evaluation template @param feedback: HTML feedback @return: Rendered html """ - context={'msg' : feedback, 'id' : "1", 'rows' : 50, 'cols' : 50} - html= render_to_string('open_ended_evaluation.html', context) + context = {'msg': feedback, 'id': "1", 'rows': 50, 'cols': 50} + html = render_to_string('open_ended_evaluation.html', context) return html def handle_ajax(self, dispatch, get, system): @@ -494,10 +497,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): handlers = { 'save_answer': self.save_answer, 'score_update': self.update_score, - 'save_post_assessment' : self.message_post, - 'skip_post_assessment' : self.skip_post_assessment, - 'check_for_score' : self.check_for_score, - } + 'save_post_assessment': self.message_post, + 'skip_post_assessment': self.skip_post_assessment, + 'check_for_score': self.check_for_score, + } if dispatch not in handlers: return 'Error' @@ -508,7 +511,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): d.update({ 'progress_changed': after != before, 'progress_status': Progress.to_js_status_str(after), - }) + }) return json.dumps(d, cls=ComplexEncoder) def check_for_score(self, get, system): @@ -519,7 +522,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): @return: Returns the current state """ state = self.state - return {'state' : state} + return {'state': state} def save_answer(self, get, system): """ @@ -545,7 +548,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self.send_to_grader(get['student_answer'], system) self.change_state(self.ASSESSING) - return {'success': True,} + return {'success': True, } def update_score(self, get, system): """ @@ -571,11 +574,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): latest = self.latest_answer() previous_answer = latest if latest is not None else self.initial_display post_assessment = self.latest_post_assessment() - score= self.latest_score() + score = self.latest_score() correct = 'correct' if self.is_submission_correct(score) else 'incorrect' else: - post_assessment="" - correct="" + post_assessment = "" + correct = "" previous_answer = self.initial_display context = { @@ -583,17 +586,18 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'previous_answer': previous_answer, 'state': self.state, 'allow_reset': self._allow_reset(), - 'rows' : 30, - 'cols' : 80, - 'id' : 'open_ended', - 'msg' : post_assessment, - 'child_type' : 'openended', - 'correct' : correct, - } + 'rows': 30, + 'cols': 80, + 'id': 'open_ended', + 'msg': post_assessment, + 'child_type': 'openended', + 'correct': correct, + } log.debug(context) html = system.render_template('open_ended.html', context) return html + class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding open ended response questions to courses @@ -627,7 +631,7 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """Assumes that xml_object has child k""" return xml_object.xpath(k)[0] - return {'oeparam': parse('openendedparam'),} + return {'oeparam': parse('openendedparam'), } def definition_to_xml(self, resource_fs): diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index ce1b15074f..aa83a35c9d 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -63,11 +63,11 @@ class OpenEndedChild(): DONE = 'done' #This is used to tell students where they are at in the module - HUMAN_NAMES={ - 'initial' : 'Started', - 'assessing' : 'Being scored', - 'post_assessment' : 'Scoring finished', - 'done' : 'Problem complete', + HUMAN_NAMES = { + 'initial': 'Started', + 'assessing': 'Being scored', + 'post_assessment': 'Scoring finished', + 'done': 'Problem complete', } def __init__(self, system, location, definition, descriptor, static_data, @@ -84,7 +84,7 @@ class OpenEndedChild(): # Scores are on scale from 0 to max_score self.history = instance_state.get('history', []) - self.state = instance_state.get('state', 'initial') + self.state = instance_state.get('state', self.INITIAL) self.created = instance_state.get('created', "False") @@ -171,8 +171,8 @@ class OpenEndedChild(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, - 'created' : "False", - } + 'created': "False", + } return json.dumps(state) def _allow_reset(self): @@ -244,8 +244,8 @@ class OpenEndedChild(): @param score: Numeric score. @return: Boolean correct. """ - correct=False - if(isinstance(score,(int, long, float, complex))): + correct = False + if(isinstance(score, (int, long, float, complex))): score_ratio = int(score) / float(self.max_score()) correct = (score_ratio >= 0.66) return correct @@ -255,7 +255,7 @@ class OpenEndedChild(): Checks to see if the last response in the module is correct. @return: 'correct' if correct, otherwise 'incorrect' """ - score=self.get_score()['score'] + score = self.get_score()['score'] correctness = 'correct' if self.is_submission_correct(score) else 'incorrect' return correctness diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 88632a38d0..870f3ea169 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -40,6 +40,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): """ + def setup_response(self, system, location, definition, descriptor): """ Sets up the module @@ -76,7 +77,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): 'initial_message': self.get_message_html(), 'state': self.state, 'allow_reset': self._allow_reset(), - 'child_type' : 'selfassessment', + 'child_type': 'selfassessment', } html = system.render_template('self_assessment_prompt.html', context) @@ -112,7 +113,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): }) return json.dumps(d, cls=ComplexEncoder) - def get_rubric_html(self,system): + def get_rubric_html(self, system): """ Return the appropriate version of the rubric, based on the state. """ @@ -121,8 +122,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): # we'll render it context = {'rubric': self.rubric, - 'max_score' : self._max_score, - } + 'max_score': self._max_score, + } if self.state == self.ASSESSING: context['read_only'] = False @@ -133,7 +134,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): return system.render_template('self_assessment_rubric.html', context) - def get_hint_html(self,system): + def get_hint_html(self, system): """ Return the appropriate version of the hint view, based on state. """ @@ -201,7 +202,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): return { 'success': True, 'rubric_html': self.get_rubric_html(system) - } + } def save_assessment(self, get, system): """ @@ -228,7 +229,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): self.record_latest_score(score) - d = {'success': True,} + d = {'success': True, } if score == self.max_score(): self.change_state(self.DONE) @@ -264,7 +265,6 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): 'allow_reset': self._allow_reset()} - class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): """ Module for adding self assessment questions to courses @@ -302,7 +302,7 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): return {'submitmessage': parse('submitmessage'), 'hintprompt': parse('hintprompt'), - } + } def definition_to_xml(self, resource_fs): '''Return an xml element representing this definition.''' From 1dca370a7f9b0f328ebf1da826f1304aebd60696 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 09:50:43 -0500 Subject: [PATCH 155/171] Better initial documentation of combined open ended --- .../xmodule/xmodule/combined_open_ended_module.py | 15 ++++++++++++++- 1 file changed, 14 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 358a3b6995..17355e9ce2 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -39,6 +39,19 @@ class CombinedOpenEndedModule(XModule): """ This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc). It transitions between problems, and support arbitrary ordering. + Each combined open ended module contains one or multiple "child" modules. + Child modules track their own state, and can transition between states. They also implement get_html and + handle_ajax. + The combined open ended module transitions between child modules as appropriate, tracks its own state, and passess + ajax requests from the browser to the child module or handles them itself (in the cases of reset and next problem) + ajax actions implemented by all children are: + 'save_answer' -- Saves the student answer + 'save_assessment' -- Saves the student assessment (or external grader assessment) + 'save_post_assessment' -- saves a post assessment (hint, feedback on feedback, etc) + ajax actions implemented by combined open ended module are: + 'reset' -- resets the whole combined open ended module and returns to the first child module + 'next_problem' -- moves to the next child module + 'get_results' -- gets results from a given child module """ STATE_VERSION = 1 @@ -163,7 +176,7 @@ class CombinedOpenEndedModule(XModule): def child_modules(self): """ - Returns the functions associated with the child modules in a dictionary. This makes writing functions + Returns the constructors associated with the child modules in a dictionary. This makes writing functions simpler (saves code duplication) Input: None Output: A dictionary of dictionaries containing the descriptor functions and module functions From 52164f58ffb0edf577343df6856c8488457aedae Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 11:39:32 -0500 Subject: [PATCH 156/171] Fix self.get call in peer grading service --- lms/djangoapps/open_ended_grading/grading_service.py | 1 + lms/djangoapps/open_ended_grading/peer_grading_service.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/open_ended_grading/grading_service.py b/lms/djangoapps/open_ended_grading/grading_service.py index 96bd931448..7362411daa 100644 --- a/lms/djangoapps/open_ended_grading/grading_service.py +++ b/lms/djangoapps/open_ended_grading/grading_service.py @@ -62,6 +62,7 @@ class GradingService(object): """ Make a get request to the grading controller """ + log.debug(params) op = lambda: self.session.get(url, allow_redirects=allow_redirects, params=params) diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py index 859499ff7e..9ef0383fb5 100644 --- a/lms/djangoapps/open_ended_grading/peer_grading_service.py +++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py @@ -81,7 +81,7 @@ class PeerGradingService(GradingService): self.get_problem_list_url = self.url + '/get_problem_list/' def get_next_submission(self, problem_location, grader_id): - response = self.get(self.get_next_submission_url, False, + response = self.get(self.get_next_submission_url, {'location': problem_location, 'grader_id': grader_id}) return response From 7febe2c2c811742d73d153cfd7afbe19025ca23d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 12:43:57 -0500 Subject: [PATCH 157/171] Integrate minimal rubric display --- .../css/combinedopenended/display.scss | 45 ++++++++ .../lib/xmodule/xmodule/open_ended_module.py | 109 +++++++++++++++++- lms/templates/open_ended_feedback.html | 1 + lms/templates/open_ended_rubric.html | 17 +++ 4 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 lms/templates/open_ended_rubric.html diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index b5eb4b52e6..75e87e1f07 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -576,4 +576,49 @@ section.open-ended-child { font-size: 0.9em; } + .rubric { + tr { + margin:10px 0px; + height: 100%; + } + td { + padding: 20px 0px; + margin: 10px 0px; + height: 100%; + } + th { + padding: 5px; + margin: 5px; + } + label, + .view-only { + margin:10px; + position: relative; + padding: 15px; + width: 200px; + height:100%; + display: inline-block; + min-height: 50px; + min-width: 50px; + background-color: #CCC; + font-size: 1em; + } + .grade { + position: absolute; + bottom:0px; + right:0px; + margin:10px; + } + .selected-grade { + background: #666; + color: white; + } + input[type=radio]:checked + label { + background: #666; + color: white; } + input[class='score-selection'] { + display: none; + } + } + } diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 45d6501816..e4f74a9124 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -376,7 +376,11 @@ class OpenEndedModule(openendedchild.OpenEndedChild): Return error message or feedback template """ + log.debug(response_items) + rubric_feedback="" feedback = self._convert_longform_feedback_to_html(response_items) + if response_items['rubric_scores_complete']: + rubric_feedback = self.render_rubric(response_items['rubric_xml']) if not response_items['success']: return system.render_template("open_ended_error.html", @@ -386,6 +390,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'grader_type': response_items['grader_type'], 'score': "{0} / {1}".format(response_items['score'], self.max_score()), 'feedback': feedback, + 'rubric_feedback' : rubric_feedback }) return feedback_template @@ -429,7 +434,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): log.error(error_message) fail['feedback'] = error_message return fail - #This is to support peer grading + #This is to support peer grading if isinstance(score_result['score'], list): feedback_items = [] for i in xrange(0, len(score_result['score'])): @@ -439,7 +444,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'grader_type': score_result['grader_type'], 'success': score_result['success'], 'grader_id': score_result['grader_id'][i], - 'submission_id': score_result['submission_id'] + 'submission_id': score_result['submission_id'], + 'rubric_scores_complete' : score_result['rubric_scores_complete'], + 'rubric_xml' : score_result['rubric_xml'], } feedback_items.append(self._format_feedback(new_score_result)) if join_feedback: @@ -597,6 +604,104 @@ class OpenEndedModule(openendedchild.OpenEndedChild): html = system.render_template('open_ended.html', context) return html + def render_rubric(self, rubric_xml): + rubric_categories = OpenEndedModule.extract_rubric_categories(rubric_xml) + html = render_to_string('open_ended_rubric.html', rubric_categories) + return html + + @staticmethod + def extract_rubric_categories(element): + ''' + Contstruct a list of categories such that the structure looks like: + [ { category: "Category 1 Name", + options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}] + }, + { category: "Category 2 Name", + options: [{text: "Option 1 Name", points: 0}, + {text: "Option 2 Name", points: 1}, + {text: "Option 3 Name", points: 2]}] + + ''' + element = etree.fromstring(element) + categories = [] + for category in element: + if category.tag != 'category': + raise Exception("[capa.inputtypes.extract_categories] Expected a tag: got {0} instead".format(category.tag)) + else: + categories.append(OpenEndedModule.extract_category(category)) + return categories + + @staticmethod + def extract_category(category): + ''' + construct an individual category + {category: "Category 1 Name", + options: [{text: "Option 1 text", points: 1}, + {text: "Option 2 text", points: 2}]} + + all sorting and auto-point generation occurs in this function + ''' + descriptionxml = category[0] + scorexml = category[1] + optionsxml = category[2:] + + # parse description + if descriptionxml.tag != 'description': + raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag)) + + if scorexml.tag != 'score': + raise Exception("[extract_category]: expected score tag, got {0} instead".format(descriptionxml.tag)) + + description = descriptionxml.text + score = int(scorexml.text) + + cur_points = 0 + options = [] + autonumbering = True + # parse options + for option in optionsxml: + if option.tag != 'option': + raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag)) + else: + pointstr = option.get("points") + if pointstr: + autonumbering = False + # try to parse this into an int + try: + points = int(pointstr) + except ValueError: + raise Exception("[extract_category]: expected points to have int, got {0} instead".format(pointstr)) + elif autonumbering: + # use the generated one if we're in the right mode + points = cur_points + cur_points = cur_points + 1 + else: + raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.") + optiontext = option.text + options.append({'text': option.text, 'points': points}) + + # sort and check for duplicates + options = sorted(options, key=lambda option: option['points']) + OpenEndedModule.validate_options(options) + + return {'description': description, 'options': options, 'score' : score} + + @staticmethod + def validate_options(options): + ''' + Validates a set of options. This can and should be extended to filter out other bad edge cases + ''' + if len(options) == 0: + raise Exception("[extract_category]: no options associated with this category") + if len(options) == 1: + return + prev = options[0]['points'] + for option in options[1:]: + if prev == option['points']: + raise Exception("[extract_category]: found duplicate point values between two different options") + else: + prev = option['points'] + class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/lms/templates/open_ended_feedback.html b/lms/templates/open_ended_feedback.html index cb90006456..d8aa3d1a9e 100644 --- a/lms/templates/open_ended_feedback.html +++ b/lms/templates/open_ended_feedback.html @@ -12,5 +12,6 @@
${ feedback | n}
+ ${rubric_feedback | n}
\ No newline at end of file diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html new file mode 100644 index 0000000000..f89cecee80 --- /dev/null +++ b/lms/templates/open_ended_rubric.html @@ -0,0 +1,17 @@ + + % for i in range(len(categories)): + <% category = categories[i] %> + + + % for j in range(len(category['options'])): + <% option = category['options'][j] %> + + % endfor + + % endfor +
${category['description']} +
+ ${option['text']} +
[${option['points']} points]
+
+
\ No newline at end of file From 9a752f51c74e93533f3a4e16c182443242f8d75c Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 12:48:15 -0500 Subject: [PATCH 158/171] Fix rubric return, add in score field --- common/lib/xmodule/xmodule/open_ended_module.py | 2 +- lms/templates/open_ended_rubric.html | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e4f74a9124..caa4701f14 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -606,7 +606,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): def render_rubric(self, rubric_xml): rubric_categories = OpenEndedModule.extract_rubric_categories(rubric_xml) - html = render_to_string('open_ended_rubric.html', rubric_categories) + html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) return html @staticmethod diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index f89cecee80..36de4fff2e 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -1,8 +1,9 @@ - % for i in range(len(categories)): - <% category = categories[i] %> + % for i in range(len(rubric_categories)): + <% category = rubric_categories[i] %> + % for j in range(len(category['options'])): <% option = category['options'][j] %> - + % for j in range(len(category['options'])): <% option = category['options'][j] %> - + % if category['has_score'] == True: + + % endif % for j in range(len(category['options'])): <% option = category['options'][j] %> % if category['has_score'] == True: +
+ % endif % for j in range(len(category['options'])): <% option = category['options'][j] %> % endfor From 16f06ae7b79a0feecf7e3610b587fe90d67bb542 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 11:57:09 -0500 Subject: [PATCH 166/171] Fix some minor html --- lms/templates/open_ended_rubric.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 889e771263..0048ff578d 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -4,9 +4,9 @@ % if category['has_score'] == True: -
-
- + % endif % for j in range(len(category['options'])): <% option = category['options'][j] %> From a64806656a4634fc17c17426dce0381361f97909 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 12:20:27 -0500 Subject: [PATCH 167/171] Integrate rubric into self assessment --- lms/templates/open_ended_rubric.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 0048ff578d..9f8a2ece4e 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -2,12 +2,12 @@ % for i in range(len(rubric_categories)): <% category = rubric_categories[i] %> - - % if category['has_score'] == True: - - % endif + % for j in range(len(category['options'])): <% option = category['options'][j] %>
${category['description']}${category['score']} From 682ac3455b21387befc718212747cba1bb2fd342 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 15:51:37 -0500 Subject: [PATCH 159/171] Address some comments from code review, fix rubric display --- .../xmodule/combined_open_ended_module.py | 28 ++++++++++++++----- .../lib/xmodule/xmodule/open_ended_module.py | 8 +++--- common/lib/xmodule/xmodule/openendedchild.py | 4 +-- lms/templates/open_ended_rubric.html | 2 +- 4 files changed, 28 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 17355e9ce2..15bc72abb4 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -52,6 +52,11 @@ class CombinedOpenEndedModule(XModule): 'reset' -- resets the whole combined open ended module and returns to the first child module 'next_problem' -- moves to the next child module 'get_results' -- gets results from a given child module + + Types of children. Task is synonymous with child module, so each combined open ended module + incorporates multiple children (tasks): + openendedmodule + selfassessmentmodule """ STATE_VERSION = 1 @@ -60,7 +65,6 @@ class CombinedOpenEndedModule(XModule): ASSESSING = 'assessing' INTERMEDIATE_DONE = 'intermediate_done' DONE = 'done' - TASK_TYPES = ["self", "ml", "instructor", "peer"] js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'), @@ -216,23 +220,33 @@ class CombinedOpenEndedModule(XModule): current_task_type = self.get_tag_name(self.current_task_xml) children = self.child_modules() + child_task_module = children['modules'][current_task_type] self.current_task_descriptor = children['descriptors'][current_task_type](self.system) + + #This is the xml object created from the xml definition of the current task etree_xml = etree.fromstring(self.current_task_xml) + #This sends the etree_xml object through the descriptor module of the current task, and + #returns the xml parsed by the descriptor 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 = children['modules'][current_task_type](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.task_states.append(self.current_task.get_instance_state()) self.state = self.ASSESSING elif current_task_state is None and self.current_task_number > 0: last_response_data = self.get_last_response(self.current_task_number - 1) last_response = last_response_data['response'] - current_task_state = ( - '{"state": "' + str(self.ASSESSING) + '", "version": 1, "max_score": ' + str(self._max_score) + ', ' + - '"attempts": 0, "created": "True", "history": [{"answer": "' + str(last_response) + '"}]}') - self.current_task = children['modules'][current_task_type](self.system, self.location, + current_task_state=json.dumps({ + 'state' : self.assessing, + 'version' : self.STATE_VERSION, + 'max_score' : self._max_score, + 'attempts' : 0, + 'created' : True, + 'history' : [{'answer' : str(last_response)}], + }) + 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) self.task_states.append(self.current_task.get_instance_state()) @@ -240,7 +254,7 @@ class CombinedOpenEndedModule(XModule): else: if self.current_task_number > 0 and not reset: current_task_state = self.overwrite_state(current_task_state) - self.current_task = children['modules'][current_task_type](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, instance_state=current_task_state) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index caa4701f14..e6acb6409c 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -78,10 +78,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): self._parse(oeparam, self.prompt, self.rubric, system) - if self.created == "True" and self.state == self.ASSESSING: - self.created = "False" + if self.created == True and self.state == self.ASSESSING: + self.created = False self.send_to_grader(self.latest_answer(), system) - self.created = "False" + self.created = False def _parse(self, oeparam, prompt, rubric, system): ''' @@ -379,7 +379,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): log.debug(response_items) rubric_feedback="" feedback = self._convert_longform_feedback_to_html(response_items) - if response_items['rubric_scores_complete']: + if response_items['rubric_scores_complete']==True: rubric_feedback = self.render_rubric(response_items['rubric_xml']) if not response_items['success']: diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index aa83a35c9d..2ba9528237 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -86,7 +86,7 @@ class OpenEndedChild(): self.state = instance_state.get('state', self.INITIAL) - self.created = instance_state.get('created', "False") + self.created = instance_state.get('created', False) self.attempts = instance_state.get('attempts', 0) self.max_attempts = static_data['max_attempts'] @@ -171,7 +171,7 @@ class OpenEndedChild(): 'state': self.state, 'max_score': self._max_score, 'attempts': self.attempts, - 'created': "False", + 'created': False, } return json.dumps(state) diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 36de4fff2e..c6310dea4d 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -3,7 +3,7 @@ <% category = rubric_categories[i] %>
${category['description']}${category['score']}Your Score: ${category['score']} From 7d0bb7b3fe120a6a7e75cba4f40c4bc46d938aeb Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 11 Jan 2013 16:02:09 -0500 Subject: [PATCH 160/171] Minor string to boolean fix --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 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 15bc72abb4..cd0be0aa6e 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -173,7 +173,7 @@ class CombinedOpenEndedModule(XModule): 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['created'] = True loaded_task_state['history'].append({'answer': last_response}) current_task_state = json.dumps(loaded_task_state) return current_task_state From c7339ee6644e49121637ba7c888f64906f11056b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 10:16:57 -0500 Subject: [PATCH 161/171] Minor bug fix --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 2 +- 1 file changed, 1 insertion(+), 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 cd0be0aa6e..a88acc6ffd 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -239,7 +239,7 @@ class CombinedOpenEndedModule(XModule): 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, + 'state' : self.ASSESSING, 'version' : self.STATE_VERSION, 'max_score' : self._max_score, 'attempts' : 0, From 0567e8f0aaad9b8a61dcb9e4bfb05ec6dff46adf Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 10:35:53 -0500 Subject: [PATCH 162/171] Factor out rubric rendering --- .../xmodule/combined_open_ended_rubric.py | 116 ++++++++++++++++++ .../lib/xmodule/xmodule/open_ended_module.py | 102 +-------------- lms/templates/open_ended_rubric.html | 4 +- 3 files changed, 122 insertions(+), 100 deletions(-) create mode 100644 common/lib/xmodule/xmodule/combined_open_ended_rubric.py diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py new file mode 100644 index 0000000000..445d066416 --- /dev/null +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -0,0 +1,116 @@ +from mitxmako.shortcuts import render_to_string + +class CombinedOpenEndedRubric: + + def render_rubric(self, rubric_xml): + rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml) + html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) + return html + + @staticmethod + def extract_rubric_categories(element): + ''' + Contstruct a list of categories such that the structure looks like: + [ { category: "Category 1 Name", + options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}] + }, + { category: "Category 2 Name", + options: [{text: "Option 1 Name", points: 0}, + {text: "Option 2 Name", points: 1}, + {text: "Option 3 Name", points: 2]}] + + ''' + element = etree.fromstring(element) + categories = [] + for category in element: + if category.tag != 'category': + raise Exception("[capa.inputtypes.extract_categories] Expected a tag: got {0} instead".format(category.tag)) + else: + categories.append(CombinedOpenEndedRubric.extract_category(category)) + return categories + + @staticmethod + def extract_category(category): + ''' + construct an individual category + {category: "Category 1 Name", + options: [{text: "Option 1 text", points: 1}, + {text: "Option 2 text", points: 2}]} + + all sorting and auto-point generation occurs in this function + ''' + + has_score=False + descriptionxml = category[0] + scorexml = category[1] + if score_xml.tag == "option": + optionsxml = category[1:] + else: + optionsxml = category[2:] + has_score=True + + # parse description + if descriptionxml.tag != 'description': + raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag)) + + if has_score: + if scorexml.tag != 'score': + raise Exception("[extract_category]: expected score tag, got {0} instead".format(scorexml.tag)) + + for option in optionsxml: + if option.tag != "option": + raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag)) + + description = descriptionxml.text + + if has_score: + score = int(scorexml.text) + else: + score = 0 + + cur_points = 0 + options = [] + autonumbering = True + # parse options + for option in optionsxml: + if option.tag != 'option': + raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag)) + else: + pointstr = option.get("points") + if pointstr: + autonumbering = False + # try to parse this into an int + try: + points = int(pointstr) + except ValueError: + raise Exception("[extract_category]: expected points to have int, got {0} instead".format(pointstr)) + elif autonumbering: + # use the generated one if we're in the right mode + points = cur_points + cur_points = cur_points + 1 + else: + raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.") + optiontext = option.text + options.append({'text': option.text, 'points': points}) + + # sort and check for duplicates + options = sorted(options, key=lambda option: option['points']) + CombinedOpenEndedRubric.validate_options(options) + + return {'description': description, 'options': options, 'score' : score, 'has_score' : has_score} + + @staticmethod + def validate_options(options): + ''' + Validates a set of options. This can and should be extended to filter out other bad edge cases + ''' + if len(options) == 0: + raise Exception("[extract_category]: no options associated with this category") + if len(options) == 1: + return + prev = options[0]['points'] + for option in options[1:]: + if prev == option['points']: + raise Exception("[extract_category]: found duplicate point values between two different options") + else: + prev = option['points'] \ No newline at end of file diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index e6acb6409c..11f96c9848 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -35,6 +35,8 @@ from numpy import median from datetime import datetime +from combined_open_ended_rubric import CombinedOpenEndedRubric + log = logging.getLogger("mitx.courseware") class OpenEndedModule(openendedchild.OpenEndedChild): @@ -380,7 +382,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): rubric_feedback="" feedback = self._convert_longform_feedback_to_html(response_items) if response_items['rubric_scores_complete']==True: - rubric_feedback = self.render_rubric(response_items['rubric_xml']) + rubric_feedback = CombinedOpenEndedRubric.render_rubric(response_items['rubric_xml']) if not response_items['success']: return system.render_template("open_ended_error.html", @@ -604,104 +606,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild): html = system.render_template('open_ended.html', context) return html - def render_rubric(self, rubric_xml): - rubric_categories = OpenEndedModule.extract_rubric_categories(rubric_xml) - html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) - return html - - @staticmethod - def extract_rubric_categories(element): - ''' - Contstruct a list of categories such that the structure looks like: - [ { category: "Category 1 Name", - options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}] - }, - { category: "Category 2 Name", - options: [{text: "Option 1 Name", points: 0}, - {text: "Option 2 Name", points: 1}, - {text: "Option 3 Name", points: 2]}] - - ''' - element = etree.fromstring(element) - categories = [] - for category in element: - if category.tag != 'category': - raise Exception("[capa.inputtypes.extract_categories] Expected a tag: got {0} instead".format(category.tag)) - else: - categories.append(OpenEndedModule.extract_category(category)) - return categories - - @staticmethod - def extract_category(category): - ''' - construct an individual category - {category: "Category 1 Name", - options: [{text: "Option 1 text", points: 1}, - {text: "Option 2 text", points: 2}]} - - all sorting and auto-point generation occurs in this function - ''' - descriptionxml = category[0] - scorexml = category[1] - optionsxml = category[2:] - - # parse description - if descriptionxml.tag != 'description': - raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag)) - - if scorexml.tag != 'score': - raise Exception("[extract_category]: expected score tag, got {0} instead".format(descriptionxml.tag)) - - description = descriptionxml.text - score = int(scorexml.text) - - cur_points = 0 - options = [] - autonumbering = True - # parse options - for option in optionsxml: - if option.tag != 'option': - raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag)) - else: - pointstr = option.get("points") - if pointstr: - autonumbering = False - # try to parse this into an int - try: - points = int(pointstr) - except ValueError: - raise Exception("[extract_category]: expected points to have int, got {0} instead".format(pointstr)) - elif autonumbering: - # use the generated one if we're in the right mode - points = cur_points - cur_points = cur_points + 1 - else: - raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.") - optiontext = option.text - options.append({'text': option.text, 'points': points}) - - # sort and check for duplicates - options = sorted(options, key=lambda option: option['points']) - OpenEndedModule.validate_options(options) - - return {'description': description, 'options': options, 'score' : score} - - @staticmethod - def validate_options(options): - ''' - Validates a set of options. This can and should be extended to filter out other bad edge cases - ''' - if len(options) == 0: - raise Exception("[extract_category]: no options associated with this category") - if len(options) == 1: - return - prev = options[0]['points'] - for option in options[1:]: - if prev == option['points']: - raise Exception("[extract_category]: found duplicate point values between two different options") - else: - prev = option['points'] - class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor): """ diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index c6310dea4d..3eee9600e2 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -3,7 +3,9 @@ <% category = rubric_categories[i] %>
${category['description']}Your Score: ${category['score']} Your Score: ${category['score']} From 2b4929562a046d4d7a1fa2954309fd4397c797a2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 10:39:58 -0500 Subject: [PATCH 163/171] Fix up rubric rendering --- .../lib/xmodule/xmodule/combined_open_ended_rubric.py | 10 +++++++--- common/lib/xmodule/xmodule/self_assessment_module.py | 6 +++++- lms/templates/self_assessment_rubric.html | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index 445d066416..2dd3eb587b 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -2,9 +2,13 @@ from mitxmako.shortcuts import render_to_string class CombinedOpenEndedRubric: - def render_rubric(self, rubric_xml): - rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml) - html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) + @staticmethod + def render_rubric(rubric_xml): + try: + rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml) + html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) + except: + html = rubric_xml return html @staticmethod diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index 870f3ea169..940b61c557 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -21,6 +21,8 @@ from .xml_module import XmlDescriptor from xmodule.modulestore import Location import openendedchild +from combined_open_ended_rubric import CombinedOpenEndedRubric + log = logging.getLogger("mitx.courseware") class SelfAssessmentModule(openendedchild.OpenEndedChild): @@ -120,8 +122,10 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild): if self.state == self.INITIAL: return '' + rubric_html = CombinedOpenEndedRubric.render_rubric(self.rubric) + # we'll render it - context = {'rubric': self.rubric, + context = {'rubric': rubric_html, 'max_score': self._max_score, } diff --git a/lms/templates/self_assessment_rubric.html b/lms/templates/self_assessment_rubric.html index 5bcb3bba93..2d32ffe8d3 100644 --- a/lms/templates/self_assessment_rubric.html +++ b/lms/templates/self_assessment_rubric.html @@ -1,7 +1,7 @@

Self-assess your answer with this rubric:

- ${rubric} + ${rubric | n }
% if not read_only: From 59e5b494a9e3ad8b89bab40f6ffb4766f149b625 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 10:51:17 -0500 Subject: [PATCH 164/171] Proper rubric rendering for self assessment --- common/lib/xmodule/xmodule/combined_open_ended_rubric.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index 2dd3eb587b..a0ce900756 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -1,4 +1,8 @@ from mitxmako.shortcuts import render_to_string +import logging +from lxml import etree + +log=logging.getLogger(__name__) class CombinedOpenEndedRubric: @@ -8,6 +12,7 @@ class CombinedOpenEndedRubric: rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml) html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) except: + log.exception("Could not parse the rubric.") html = rubric_xml return html @@ -47,7 +52,7 @@ class CombinedOpenEndedRubric: has_score=False descriptionxml = category[0] scorexml = category[1] - if score_xml.tag == "option": + if scorexml.tag == "option": optionsxml = category[1:] else: optionsxml = category[2:] From 3e0cf9d234a0df720b6cc2e4a4ad4c50538d1655 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 11:53:23 -0500 Subject: [PATCH 165/171] Make rubric render more nicely --- .../xmodule/combined_open_ended_rubric.py | 6 +- .../css/combinedopenended/display.scss | 92 ++++++++++--------- lms/templates/open_ended_rubric.html | 12 ++- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py index a0ce900756..0b2ca1ca2c 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_rubric.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_rubric.py @@ -100,7 +100,11 @@ class CombinedOpenEndedRubric: else: raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.") optiontext = option.text - options.append({'text': option.text, 'points': points}) + selected = False + if has_score: + if points == score: + selected = True + options.append({'text': option.text, 'points': points, 'selected' : selected}) # sort and check for duplicates options = sorted(options, key=lambda option: option['points']) diff --git a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss index 75e87e1f07..a58e30f1e2 100644 --- a/common/lib/xmodule/xmodule/css/combinedopenended/display.scss +++ b/common/lib/xmodule/xmodule/css/combinedopenended/display.scss @@ -219,6 +219,53 @@ div.result-container { } } +div.result-container, section.open-ended-child { + .rubric { + tr { + margin:10px 0px; + height: 100%; + } + td { + padding: 20px 0px; + margin: 10px 0px; + height: 100%; + } + th { + padding: 5px; + margin: 5px; + } + label, + .view-only { + margin:10px; + position: relative; + padding: 15px; + width: 200px; + height:100%; + display: inline-block; + min-height: 50px; + min-width: 50px; + background-color: #CCC; + font-size: 1em; + } + .grade { + position: absolute; + bottom:0px; + right:0px; + margin:10px; + } + .selected-grade { + background: #666; + color: white; + } + input[type=radio]:checked + label { + background: #666; + color: white; } + input[class='score-selection'] { + display: none; + } + } +} + section.open-ended-child { @media print { display: block; @@ -576,49 +623,4 @@ section.open-ended-child { font-size: 0.9em; } - .rubric { - tr { - margin:10px 0px; - height: 100%; - } - td { - padding: 20px 0px; - margin: 10px 0px; - height: 100%; - } - th { - padding: 5px; - margin: 5px; - } - label, - .view-only { - margin:10px; - position: relative; - padding: 15px; - width: 200px; - height:100%; - display: inline-block; - min-height: 50px; - min-width: 50px; - background-color: #CCC; - font-size: 1em; - } - .grade { - position: absolute; - bottom:0px; - right:0px; - margin:10px; - } - .selected-grade { - background: #666; - color: white; - } - input[type=radio]:checked + label { - background: #666; - color: white; } - input[class='score-selection'] { - display: none; - } - } - } diff --git a/lms/templates/open_ended_rubric.html b/lms/templates/open_ended_rubric.html index 3eee9600e2..889e771263 100644 --- a/lms/templates/open_ended_rubric.html +++ b/lms/templates/open_ended_rubric.html @@ -4,14 +4,24 @@
${category['description']}Your Score: ${category['score']}
${option['text']} -
[${option['points']} points]
+ % if option.has_key('selected'): + % if option['selected'] == True: +
[${option['points']} points]
+ %else: +
[${option['points']} points]
+ % endif + % else: +
[${option['points']} points]
+ %endif
${category['description']}Your Score: ${category['score']} + Your Score: ${category['score']} +
${category['description']} - Your Score: ${category['score']} - + ${category['description']} + % if category['has_score'] == True: + (Your score: ${category['score']}) + % endif + From 0e11deffcf8279501a70ae3047bfd19fdc12c018 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 12:31:50 -0500 Subject: [PATCH 168/171] Fix staff and peer grading settings to not fail catastrophically --- lms/envs/aws.py | 4 ++-- lms/envs/common.py | 20 ++++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 0516bddc56..98c65e7cb4 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -76,8 +76,8 @@ DATABASES = AUTH_TOKENS['DATABASES'] XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] -STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE') - +STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', STAFF_GRADING_INTERFACE) +PEER_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', PEER_GRADING_INTERFACE) PEARSON_TEST_USER = "pearsontest" PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") diff --git a/lms/envs/common.py b/lms/envs/common.py index 88cf09502d..0364a8b6f8 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -329,12 +329,28 @@ WIKI_LINK_DEFAULT_LEVEL = 2 ################################# Staff grading config ##################### -STAFF_GRADING_INTERFACE = None +#By setting up the default settings with an incorrect user name and password, +# will get an error when attempting to connect +STAFF_GRADING_INTERFACE = { + 'url': 'http://sandbox-grader-001.m.edx.org/staff_grading', + 'username': 'incorrect_user', + 'password': 'incorrect_pass', + } + # Used for testing, debugging MOCK_STAFF_GRADING = False ################################# Peer grading config ##################### -PEER_GRADING_INTERFACE = None + +#By setting up the default settings with an incorrect user name and password, +# will get an error when attempting to connect +PEER_GRADING_INTERFACE = { + 'url': 'http://sandbox-grader-001.m.edx.org/peer_grading', + 'username': 'incorrect_user', + 'password': 'incorrect_pass', + } + +# Used for testing, debugging MOCK_PEER_GRADING = False ################################# Jasmine ################################### From 1316728aaa851ad6666acfa77c90eaf5092eecd5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 12:32:31 -0500 Subject: [PATCH 169/171] Minor naming fix --- lms/envs/aws.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 98c65e7cb4..7b8c48f4af 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -77,7 +77,7 @@ DATABASES = AUTH_TOKENS['DATABASES'] XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE'] STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', STAFF_GRADING_INTERFACE) -PEER_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE', PEER_GRADING_INTERFACE) +PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE', PEER_GRADING_INTERFACE) PEARSON_TEST_USER = "pearsontest" PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD") From 19f79fa36406038031abf62c1bd2b138aa8ce9d1 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Mon, 14 Jan 2013 12:40:17 -0500 Subject: [PATCH 170/171] Prune deleted remote branches --- jenkins/quality.sh | 2 ++ jenkins/test.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/jenkins/quality.sh b/jenkins/quality.sh index 4cf26d76bf..56217af874 100755 --- a/jenkins/quality.sh +++ b/jenkins/quality.sh @@ -3,6 +3,8 @@ set -e set -x +git remote prune origin + # Reset the submodule, in case it changed git submodule foreach 'git reset --hard HEAD' diff --git a/jenkins/test.sh b/jenkins/test.sh index 8a96024785..7a61e914b7 100755 --- a/jenkins/test.sh +++ b/jenkins/test.sh @@ -15,6 +15,8 @@ function github_mark_failed_on_exit { trap '[ $? == "0" ] || github_status state:failure "failed"' EXIT } +git remote prune origin + github_mark_failed_on_exit github_status state:pending "is running" From ad1e73fa861e3cae07ad2cf4c350f400896ed6ab Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Mon, 14 Jan 2013 13:12:38 -0500 Subject: [PATCH 171/171] Clean up capa by removing message post actions that aren't needed --- common/lib/capa/capa/capa_problem.py | 18 ------------------ common/lib/xmodule/xmodule/capa_module.py | 15 --------------- 2 files changed, 33 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index efc96fc717..2eaa0e4286 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -186,24 +186,6 @@ class LoncapaProblem(object): maxscore += responder.get_max_score() return maxscore - def message_post(self,event_info): - """ - Handle an ajax post that contains feedback on feedback - Returns a boolean success variable - Note: This only allows for feedback to be posted back to the grading controller for the first - open ended response problem on each page. Multiple problems will cause some sync issues. - TODO: Handle multiple problems on one page sync issues. - """ - success=False - message = "Could not find a valid responder." - log.debug("in lcp") - for responder in self.responders.values(): - if hasattr(responder, 'handle_message_post'): - success, message = responder.handle_message_post(event_info) - if success: - break - return success, message - def get_score(self): """ Compute score for this problem. The score is the number of points awarded. diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 08f503f127..1da271072a 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -380,7 +380,6 @@ class CapaModule(XModule): 'problem_save': self.save_problem, 'problem_show': self.get_answer, 'score_update': self.update_score, - 'message_post' : self.message_post, } if dispatch not in handlers: @@ -395,20 +394,6 @@ class CapaModule(XModule): }) return json.dumps(d, cls=ComplexEncoder) - def message_post(self, get): - """ - Posts a message from a form to an appropriate location - """ - event_info = dict() - event_info['state'] = self.lcp.get_state() - event_info['problem_id'] = self.location.url() - event_info['student_id'] = self.system.anonymous_student_id - event_info['survey_responses']= get - - success, message = self.lcp.message_post(event_info) - - return {'success' : success, 'message' : message} - def closed(self): ''' Is the student still allowed to submit answers? ''' if self.attempts == self.max_attempts: