From c2e5c607136e93a8c63d1aa073fe0ef1756a24f3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 13 Jun 2013 15:34:00 -0400 Subject: [PATCH 01/13] Remove correct/incorrect message, and auto-add in needed markdown for studio --- .../lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee | 4 ++++ lms/templates/combinedopenended/openended/open_ended.html | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee index 1b7f9bb4fbfe..2f58e86cb2f1 100644 --- a/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee +++ b/common/lib/xmodule/xmodule/js/src/combinedopenended/edit.coffee @@ -50,6 +50,10 @@ Write a persuasive essay to a newspaper reflecting your vies on censorship in li mode: null }) @setCurrentEditor(@markdown_editor) + selection = @markdown_editor.getSelection() + #Auto-add in the needed template if it isn't already in there. + if(@markdown_editor.getValue() == "") + @markdown_editor.setValue(OpenEndedMarkdownEditingDescriptor.promptTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.rubricTemplate + "\n" + OpenEndedMarkdownEditingDescriptor.tasksTemplate) # Add listeners for toolbar buttons (only present for markdown editor) @element.on('click', '.xml-tab', @onShowXMLButton) @element.on('click', '.format-buttons a', @onToolbarButton) diff --git a/lms/templates/combinedopenended/openended/open_ended.html b/lms/templates/combinedopenended/openended/open_ended.html index 9fb136cee69c..909ef1583803 100644 --- a/lms/templates/combinedopenended/openended/open_ended.html +++ b/lms/templates/combinedopenended/openended/open_ended.html @@ -10,10 +10,6 @@

Response

% 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 eta_message is not None: From cd49e1bb686f747969d13e875111f448cf5b71bb Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 13 Jun 2013 16:00:28 -0400 Subject: [PATCH 02/13] Check to see if a location is valid before displaying it for instructor grading --- .../open_ended_grading/staff_grading_service.py | 11 ++++++++++- lms/djangoapps/open_ended_grading/utils.py | 16 ++++++++++++++++ lms/djangoapps/open_ended_grading/views.py | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 lms/djangoapps/open_ended_grading/utils.py diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py index 2c611b448181..a2d905be0b29 100644 --- a/lms/djangoapps/open_ended_grading/staff_grading_service.py +++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py @@ -15,6 +15,7 @@ from student.models import unique_id_for_user from xmodule.x_module import ModuleSystem from mitxmako.shortcuts import render_to_string +from utils import does_location_exist log = logging.getLogger(__name__) @@ -240,7 +241,6 @@ def get_next(request, course_id): return HttpResponse(_get_next(course_id, grader_id, location), mimetype="application/json") - def get_problem_list(request, course_id): """ Get all the problems for the given course id @@ -266,6 +266,15 @@ def get_problem_list(request, course_id): _check_access(request.user, course_id) try: response = staff_grading_service().get_problem_list(course_id, unique_id_for_user(request.user)) + response = json.loads(response) + problem_list = response['problem_list'] + valid_problem_list = [] + for i in xrange(0,len(problem_list)): + if does_location_exist(course_id, problem_list[i]['location']): + valid_problem_list.append(problem_list[i]) + response['problem_list'] = valid_problem_list + response = json.dumps(response) + return HttpResponse(response, mimetype="application/json") except GradingServiceError: diff --git a/lms/djangoapps/open_ended_grading/utils.py b/lms/djangoapps/open_ended_grading/utils.py new file mode 100644 index 000000000000..763497739769 --- /dev/null +++ b/lms/djangoapps/open_ended_grading/utils.py @@ -0,0 +1,16 @@ +from xmodule.modulestore import search +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.exceptions import ItemNotFoundError + +def does_location_exist(course_id, location): + """ + Checks to see if a valid module exists at a given location (ie has not been deleted) + course_id - string course id + location - string location + """ + try: + search.path_to_location(modulestore(), course_id, location) + return True + except ItemNotFoundError: + #If the problem cannot be found at the location received from the grading controller server, it has been deleted by the course author. + return False diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index a914e434a9ce..797c90f9a4e2 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -179,6 +179,7 @@ def student_problem_list(request, course_id): error_text = "" problem_list = [] base_course_url = reverse('courses') + list_to_remove = [] try: #Get list of all open ended problems that the grading server knows about @@ -192,7 +193,6 @@ def student_problem_list(request, course_id): problem_list = problem_list_dict['problem_list'] #A list of problems to remove (problems that can't be found in the course) - list_to_remove = [] for i in xrange(0, len(problem_list)): try: #Try to load each problem in the courseware to get links to them From 9b14ea790c30f25792c05bf6901c3ae44cc4c603 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 27 Jun 2013 16:20:58 -0400 Subject: [PATCH 03/13] Properly increment student attempts --- .../open_ended_grading_classes/combined_open_ended_modulev1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 1fe62035e65e..c99d0915cbb3 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -653,6 +653,7 @@ def reset(self, data): ).format(self.student_attempts, self.attempts) } self.state = self.INITIAL + self.student_attempts +=1 self.ready_to_reset = False for i in xrange(0, len(self.task_xml)): self.current_task_number = i From 8a6c8b5ab0ac79d5e458e95fc743351dd0c6f8cc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Jun 2013 14:10:17 -0400 Subject: [PATCH 04/13] Add test for incrementing student attempts --- .../combined_open_ended_modulev1.py | 4 +- .../xmodule/tests/test_combined_open_ended.py | 51 +++++++++++++++++++ .../SampleQuestion1Attempt.xml | 24 +++++++++ .../test/data/open_ended/course/2012_Fall.xml | 1 + 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 common/test/data/open_ended/combinedopenended/SampleQuestion1Attempt.xml diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index c99d0915cbb3..62c941766017 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -643,7 +643,8 @@ def reset(self, data): if not self.ready_to_reset: return self.out_of_sync_error(data) - if self.student_attempts > self.attempts: + self.student_attempts +=1 + if self.student_attempts >= self.attempts: return { 'success': False, # This is a student_facing_error @@ -653,7 +654,6 @@ def reset(self, data): ).format(self.student_attempts, self.attempts) } self.state = self.INITIAL - self.student_attempts +=1 self.ready_to_reset = False for i in xrange(0, len(self.task_xml)): self.current_task_number = i diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index e1f8d135deaa..dc0023f80d89 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -629,3 +629,54 @@ def test_open_ended_flow_correct(self): #reset the problem module.handle_ajax("reset", {}) self.assertEqual(module.state, "initial") + +class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): + """ + Test the student flow in the combined open ended xmodule + """ + problem_location = Location(["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion1Attempt"]) + answer = "blah blah" + assessment = [0, 1] + hint = "blah" + + def setUp(self): + self.test_system = get_test_system() + self.test_system.xqueue['interface'] = Mock( + send_to_queue=Mock(side_effect=[1, "queued"]) + ) + self.setup_modulestore(COURSE) + + def test_reset_fail(self): + """ + Test the flow of the module if we complete the self assessment step and then reset + @return: + """ + assessment = [0, 1] + module = self.get_module_from_location(self.problem_location, COURSE) + + #Simulate a student saving an answer + module.handle_ajax("save_answer", {"student_answer": self.answer}) + status = module.handle_ajax("get_status", {}) + self.assertTrue(isinstance(status, basestring)) + + #Mock a student submitting an assessment + assessment_dict = MockQueryDict() + assessment_dict.update({'assessment': sum(assessment), 'score_list[]': assessment}) + module.handle_ajax("save_assessment", assessment_dict) + task_one_json = json.loads(module.task_states[0]) + self.assertEqual(json.loads(task_one_json['child_history'][0]['post_assessment']), assessment) + status = module.handle_ajax("get_status", {}) + self.assertTrue(isinstance(status, basestring)) + + #Move to the next step in the problem + module.handle_ajax("next_problem", {}) + self.assertEqual(module.current_task_number, 0) + + html = module.get_html() + self.assertTrue(isinstance(html, basestring)) + + rubric = module.handle_ajax("get_combined_rubric", {}) + self.assertTrue(isinstance(rubric, basestring)) + self.assertEqual(module.state, "done") + reset_data = json.loads(module.handle_ajax("reset", {})) + self.assertEqual(reset_data['success'], False) diff --git a/common/test/data/open_ended/combinedopenended/SampleQuestion1Attempt.xml b/common/test/data/open_ended/combinedopenended/SampleQuestion1Attempt.xml new file mode 100644 index 000000000000..9bfabca191f7 --- /dev/null +++ b/common/test/data/open_ended/combinedopenended/SampleQuestion1Attempt.xml @@ -0,0 +1,24 @@ + + + + + Writing Applications + + + + + Language Conventions + + + + + + +

Censorship in the Libraries

+

"All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us." --Katherine Paterson, Author

+

Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading.

+
+ + + +
\ No newline at end of file diff --git a/common/test/data/open_ended/course/2012_Fall.xml b/common/test/data/open_ended/course/2012_Fall.xml index 32c810174b62..609d12f94c2f 100644 --- a/common/test/data/open_ended/course/2012_Fall.xml +++ b/common/test/data/open_ended/course/2012_Fall.xml @@ -1,6 +1,7 @@ + From b38a36d44a5600c44da9840b59eb2dbef08a6bdf Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Jun 2013 14:38:13 -0400 Subject: [PATCH 05/13] Fix test --- .../lib/xmodule/xmodule/tests/test_combined_open_ended.py | 8 ++++++-- .../open_ended_grading/staff_grading_service.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index dc0023f80d89..af1b6aa12b94 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -630,9 +630,9 @@ def test_open_ended_flow_correct(self): module.handle_ajax("reset", {}) self.assertEqual(module.state, "initial") -class OpenEndedModuleXmlTest(unittest.TestCase, DummyModulestore): +class OpenEndedModuleXmlAttemptTest(unittest.TestCase, DummyModulestore): """ - Test the student flow in the combined open ended xmodule + Test if student is able to reset the problem """ problem_location = Location(["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion1Attempt"]) answer = "blah blah" @@ -649,6 +649,7 @@ def setUp(self): def test_reset_fail(self): """ Test the flow of the module if we complete the self assessment step and then reset + Since the problem only allows one attempt, should fail. @return: """ assessment = [0, 1] @@ -675,8 +676,11 @@ def test_reset_fail(self): html = module.get_html() self.assertTrue(isinstance(html, basestring)) + #Module should now be done rubric = module.handle_ajax("get_combined_rubric", {}) self.assertTrue(isinstance(rubric, basestring)) self.assertEqual(module.state, "done") + + #Try to reset, should fail because only 1 attempt is allowed reset_data = json.loads(module.handle_ajax("reset", {})) self.assertEqual(reset_data['success'], False) diff --git a/lms/djangoapps/open_ended_grading/staff_grading_service.py b/lms/djangoapps/open_ended_grading/staff_grading_service.py index eda78d0b0928..a6a2847bc1a7 100644 --- a/lms/djangoapps/open_ended_grading/staff_grading_service.py +++ b/lms/djangoapps/open_ended_grading/staff_grading_service.py @@ -270,6 +270,11 @@ def get_problem_list(request, course_id): problem_list = response['problem_list'] valid_problem_list = [] for i in xrange(0,len(problem_list)): + #Needed to ensure that the 'location' key can be accessed + try: + problem_list[i] = json.loads(problem_list[i]) + except Exception: + pass if does_location_exist(course_id, problem_list[i]['location']): valid_problem_list.append(problem_list[i]) response['problem_list'] = valid_problem_list From 8930e753e4c225a16e862825aac84cb15469fb8d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Jun 2013 14:59:39 -0400 Subject: [PATCH 06/13] Set default weights --- .../xmodule/combined_open_ended_module.py | 70 ++++++++++++++----- .../xmodule/xmodule/peer_grading_module.py | 29 +++++--- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 52d98f032eab..fdc85369a14d 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -51,47 +51,85 @@ class CombinedOpenEndedFields(object): display_name = String( display_name="Display Name", help="This name appears in the horizontal navigation at the top of the page.", - default="Open Ended Grading", scope=Scope.settings + default="Open Ended Grading", + scope=Scope.settings + ) + current_task_number = Integer( + help="Current task that the student is on.", + default=0, + scope=Scope.user_state + ) + task_states = List( + help="List of state dictionaries of each task within this module.", + scope=Scope.user_state + ) + state = String( + help="Which step within the current task that the student is on.", + default="initial", + scope=Scope.user_state + ) + student_attempts = Integer( + help="Number of attempts taken by the student on this problem", + default=0, + scope=Scope.user_state ) - current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state) - task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state) - state = String(help="Which step within the current task that the student is on.", default="initial", - scope=Scope.user_state) - student_attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, - scope=Scope.user_state) ready_to_reset = Boolean( - help="If the problem is ready to be reset or not.", default=False, + help="If the problem is ready to be reset or not.", + default=False, scope=Scope.user_state ) attempts = Integer( display_name="Maximum Attempts", - help="The number of times the student can try to answer this problem.", default=1, + help="The number of times the student can try to answer this problem.", + default=1, scope=Scope.settings, values={"min" : 1 } ) - is_graded = Boolean(display_name="Graded", help="Whether or not the problem is graded.", default=False, scope=Scope.settings) + is_graded = Boolean( + display_name="Graded", + help="Whether or not the problem is graded.", + default=False, + scope=Scope.settings + ) accept_file_upload = Boolean( display_name="Allow File Uploads", - help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings + help="Whether or not the student can submit files as a response.", + default=False, + scope=Scope.settings ) skip_spelling_checks = Boolean( display_name="Disable Quality Filter", help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.", default=False, scope=Scope.settings ) - due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings) + due = Date( + help="Date that this problem is due by", + default=None, + scope=Scope.settings + ) graceperiod = String( help="Amount of time after the due date that submissions will be accepted", default=None, scope=Scope.settings ) - version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings) - data = String(help="XML data for the problem", scope=Scope.content) + version = VersionInteger( + help="Current version number", + default=DEFAULT_VERSION, + scope=Scope.settings + ) + data = String( + help="XML data for the problem", + scope=Scope.content + ) weight = Float( display_name="Problem Weight", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", - scope=Scope.settings, values={"min" : 0 , "step": ".1"} + scope=Scope.settings, values={"min" : 0 , "step": ".1"}, + default=1 + ) + markdown = String( + help="Markdown source of this module", + scope=Scope.settings ) - markdown = String(help="Markdown source of this module", scope=Scope.settings) class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 7df444a8923f..f92971796cf1 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -32,23 +32,35 @@ class PeerGradingFields(object): display_name="Show Single Problem", help='When True, only the single problem specified by "Link to Problem Location" is shown. ' 'When False, a panel is displayed with all problems available for peer grading.', - default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings + default=USE_FOR_SINGLE_LOCATION, + scope=Scope.settings ) link_to_location = String( display_name="Link to Problem Location", help='The location of the problem being graded. Only used when "Show Single Problem" is True.', - default=LINK_TO_LOCATION, scope=Scope.settings + default=LINK_TO_LOCATION, + scope=Scope.settings ) is_graded = Boolean( display_name="Graded", help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.', - default=IS_GRADED, scope=Scope.settings + default=IS_GRADED, + scope=Scope.settings + ) + due_date = Date( + help="Due date that should be displayed.", + default=None, + scope=Scope.settings) + grace_period_string = String( + help="Amount of grace to give on the due date.", + default=None, + scope=Scope.settings ) - due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings) - grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings) max_grade = Integer( - help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE, - scope=Scope.settings, values={"min": 0} + help="The maximum grade that a student can receive for this problem.", + default=MAX_SCORE, + scope=Scope.settings, + values={"min": 0} ) student_data_for_location = Dict( help="Student data for a given peer grading problem.", @@ -57,7 +69,8 @@ class PeerGradingFields(object): weight = Float( display_name="Problem Weight", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", - scope=Scope.settings, values={"min": 0, "step": ".1"} + scope=Scope.settings, values={"min": 0, "step": ".1"}, + default=1 ) From 2246944594cf2199e4c57fe371755011b4de5be8 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Fri, 28 Jun 2013 15:09:19 -0400 Subject: [PATCH 07/13] Set a default weight --- .../xmodule/combined_open_ended_module.py | 9 ++++++--- .../combined_open_ended_modulev1.py | 11 ++++++++--- .../lib/xmodule/xmodule/peer_grading_module.py | 16 ++++++++++------ 3 files changed, 24 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 fdc85369a14d..736b82e21d50 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -82,7 +82,8 @@ class CombinedOpenEndedFields(object): display_name="Maximum Attempts", help="The number of times the student can try to answer this problem.", default=1, - scope=Scope.settings, values={"min" : 1 } + scope=Scope.settings, + values={"min" : 1 } ) is_graded = Boolean( display_name="Graded", @@ -99,7 +100,8 @@ class CombinedOpenEndedFields(object): skip_spelling_checks = Boolean( display_name="Disable Quality Filter", help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.", - default=False, scope=Scope.settings + default=False, + scope=Scope.settings ) due = Date( help="Date that this problem is due by", @@ -123,7 +125,8 @@ class CombinedOpenEndedFields(object): weight = Float( display_name="Problem Weight", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", - scope=Scope.settings, values={"min" : 0 , "step": ".1"}, + scope=Scope.settings, + values={"min" : 0 , "step": ".1"}, default=1 ) markdown = String( diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 62c941766017..926bd780f71b 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -727,7 +727,12 @@ def get_score(self): """ max_score = None score = None - if self.is_scored and self.weight is not None: + + #The old default was None, so set to 1 if it is the old default weight + weight = self.weight + if weight is None: + weight = 1 + if self.is_scored: # Finds the maximum score of all student attempts and keeps it. score_mat = [] for i in xrange(0, len(self.task_states)): @@ -740,7 +745,7 @@ def get_score(self): for z in xrange(0, len(score)): if score[z] is None: score[z] = 0 - score[z] *= float(self.weight) + score[z] *= float(weight) score_mat.append(score) if len(score_mat) > 0: @@ -754,7 +759,7 @@ def get_score(self): if max_score is not None: # Weight the max score if it is not None - max_score *= float(self.weight) + max_score *= float(weight) else: # Without a max_score, we cannot have a score! score = None diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index f92971796cf1..6164a4d63598 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -57,7 +57,7 @@ class PeerGradingFields(object): scope=Scope.settings ) max_grade = Integer( - help="The maximum grade that a student can receive for this problem.", + help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE, scope=Scope.settings, values={"min": 0} @@ -214,6 +214,11 @@ def get_progress(self): def get_score(self): max_score = None score = None + weight = self.weight + + #The old default was None, so set to 1 if it is the old default weight + if weight is None: + weight = 1 score_dict = { 'score': score, 'total': max_score, @@ -238,11 +243,10 @@ def get_score(self): # Ensures that once a student receives a final score for peer grading, that it does not change. self.student_data_for_location = response - if self.weight is not None: - score = int(count_graded >= count_required and count_graded > 0) * float(self.weight) - total = self.max_grade * float(self.weight) - score_dict['score'] = score - score_dict['total'] = total + score = int(count_graded >= count_required and count_graded > 0) * float(weight) + total = self.max_grade * float(weight) + score_dict['score'] = score + score_dict['total'] = total return score_dict From d17486a9c7c0d1acb0f35be99f5f45b713f06f2f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 14:39:52 -0400 Subject: [PATCH 08/13] Update default handling --- .../xmodule/combined_open_ended_module.py | 193 ++++++++++++++---- 1 file changed, 154 insertions(+), 39 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 7d3a35c0840c..df8c7e716c23 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -29,36 +29,124 @@ DEFAULT_VERSION = 1 DEFAULT_DATA = textwrap.dedent("""\ - + + +

Censorship in the Libraries

+ +

'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author +

+ +

+ Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading. +

+ +
+ - - - Category 1 - - - - + + + Ideas + + + + + + + + + Content + + + + + + + + + Organization + + + + + + + + Style + + + + + + + + Voice + + + + + + - -

Why is the sky blue?

-
- - - - - - - Enter essay here. - This is the answer. - {"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"} - - - -
+ + + + + + + + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + + + + + + + Enter essay here. + This is the answer. + {"grader_settings" : "peer_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + + + +
""") @@ -159,17 +247,44 @@ class CombinedOpenEndedFields(object): markdown = String( help="Markdown source of this module", default=textwrap.dedent("""\ - [rubric] - + Category 1 - - The response does not incorporate what is needed for a one response. - - The response is correct for category 1. - [rubric] - [prompt] -

Why is the sky blue?

- [prompt] - [tasks] - (Self), ({1-2}AI) - [tasks] + [prompt] +

Censorship in the Libraries

+ +

'All of us can think of a book that we hope none of our children or any other children have taken off the shelf. But if I have the right to remove that book from the shelf -- that work I abhor -- then you also have exactly the same right and so does everyone else. And then we have no books left on the shelf for any of us.' --Katherine Paterson, Author +

+ +

+ Write a persuasive essay to a newspaper reflecting your vies on censorship in libraries. Do you believe that certain materials, such as books, music, movies, magazines, etc., should be removed from the shelves if they are found offensive? Support your position with convincing arguments from your own experience, observations, and/or reading. +

+ [prompt] + [rubric] + + Ideas + - Difficult for the reader to discern the main idea. Too brief or too repetitive to establish or maintain a focus. + - Attempts a main idea. Sometimes loses focus or ineffectively displays focus. + - Presents a unifying theme or main idea, but may include minor tangents. Stays somewhat focused on topic and task. + - Presents a unifying theme or main idea without going off on tangents. Stays completely focused on topic and task. + + Content + - Includes little information with few or no details or unrelated details. Unsuccessful in attempts to explore any facets of the topic. + - Includes little information and few or no details. Explores only one or two facets of the topic. + - Includes sufficient information and supporting details. (Details may not be fully developed; ideas may be listed.) Explores some facets of the topic. + - Includes in-depth information and exceptional supporting details that are fully developed. Explores all facets of the topic. + + Organization + - Ideas organized illogically, transitions weak, and response difficult to follow. + - Attempts to logically organize ideas. Attempts to progress in an order that enhances meaning, and demonstrates use of transitions. + - Ideas organized logically. Progresses in an order that enhances meaning. Includes smooth transitions. + + Style + - Contains limited vocabulary, with many words used incorrectly. Demonstrates problems with sentence patterns. + - Contains basic vocabulary, with words that are predictable and common. Contains mostly simple sentences (although there may be an attempt at more varied sentence patterns). + - Includes vocabulary to make explanations detailed and precise. Includes varied sentence patterns, including complex sentences. + + Voice + - Demonstrates language and tone that may be inappropriate to task and reader. + - Demonstrates an attempt to adjust language and tone to task and reader. + - Demonstrates effective adjustment of language and tone to task and reader. + [rubric] + [tasks] + (Self), ({4-12}AI), ({9-12}Peer) + [tasks] + """), scope=Scope.settings ) From 480e97a3fdfe4250738e24c35ae02a928494ed13 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 14:55:59 -0400 Subject: [PATCH 09/13] Cleanup and change attribute names --- .../xmodule/combined_open_ended_module.py | 57 ++++++------------- .../combined_open_ended_modulev1.py | 38 ++----------- .../xmodule/xmodule/peer_grading_module.py | 39 +++++-------- 3 files changed, 35 insertions(+), 99 deletions(-) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index df8c7e716c23..e01ae49149dd 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -13,7 +13,7 @@ log = logging.getLogger("mitx.courseware") -V1_SETTINGS_ATTRIBUTES = ["display_name", "attempts", "is_graded", "accept_file_upload", +V1_SETTINGS_ATTRIBUTES = ["display_name", "max_attempts", "graded", "accept_file_upload", "skip_spelling_checks", "due", "graceperiod", "weight"] V1_STUDENT_ATTRIBUTES = ["current_task_number", "task_states", "state", @@ -172,7 +172,7 @@ class CombinedOpenEndedFields(object): display_name = String( display_name="Display Name", help="This name appears in the horizontal navigation at the top of the page.", - default="Open Ended Grading", + default="Open Response Assessment", scope=Scope.settings ) current_task_number = Integer( @@ -189,6 +189,12 @@ class CombinedOpenEndedFields(object): default="initial", scope=Scope.user_state ) + graded = Boolean( + display_name="Graded", + help='Defines whether the student gets credit for grading this problem.', + default=False, + scope=Scope.settings + ) student_attempts = Integer( help="Number of attempts taken by the student on this problem", default=0, @@ -199,19 +205,13 @@ class CombinedOpenEndedFields(object): default=False, scope=Scope.user_state ) - attempts = Integer( + max_attempts = Integer( display_name="Maximum Attempts", help="The number of times the student can try to answer this problem.", default=1, scope=Scope.settings, values={"min" : 1 } ) - is_graded = Boolean( - display_name="Graded", - help="Whether or not the problem is graded.", - default=False, - scope=Scope.settings - ) accept_file_upload = Boolean( display_name="Allow File Uploads", help="Whether or not the student can submit files as a response.", @@ -339,37 +339,9 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): def __init__(self, *args, **kwargs): """ - 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"} - - - - + Definition file should have one or many task blocks, a rubric block, and a prompt block. + + See DEFAULT_DATA for a sample. """ XModule.__init__(self, *args, **kwargs) @@ -450,6 +422,11 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor): js_module_name = "OpenEndedMarkdownEditingDescriptor" css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/combinedopenended/edit.scss')]} + metadata_translations = { + 'is_graded': 'graded', + 'attempts': 'max_attempts', + } + def get_context(self): _context = RawDescriptor.get_context(self) _context.update({'markdown': self.markdown, diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index a9feca24f0ba..fbc846ecd361 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -78,37 +78,7 @@ def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, metadata=None, static_data=None, **kwargs): """ - 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"} - - - - + Definition file should have one or many task blocks, a rubric block, and a prompt block. See DEFAULT_DATA in combined_open_ended_module for a sample. """ @@ -131,8 +101,8 @@ def __init__(self, system, location, definition, descriptor, # Allow reset is true if student has failed the criteria to move to the next child task self.ready_to_reset = instance_state.get('ready_to_reset', False) - self.attempts = self.instance_state.get('attempts', MAX_ATTEMPTS) - self.is_scored = self.instance_state.get('is_graded', IS_SCORED) in TRUE_DICT + self.max_attempts = self.instance_state.get('max_attempts', MAX_ATTEMPTS) + self.is_scored = self.instance_state.get('graded', IS_SCORED) in TRUE_DICT self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT @@ -153,7 +123,7 @@ def __init__(self, system, location, definition, descriptor, # Static data is passed to the child modules to render self.static_data = { 'max_score': self._max_score, - 'max_attempts': self.attempts, + 'max_attempts': self.max_attempts, 'prompt': definition['prompt'], 'rubric': definition['rubric'], 'display_name': self.display_name, diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 0df47758dc3a..8f5f93318aa0 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -19,32 +19,27 @@ log = logging.getLogger(__name__) -USE_FOR_SINGLE_LOCATION = False -LINK_TO_LOCATION = "" -MAX_SCORE = 1 -IS_GRADED = False EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please notify course staff." - class PeerGradingFields(object): use_for_single_location = Boolean( display_name="Show Single Problem", help='When True, only the single problem specified by "Link to Problem Location" is shown. ' 'When False, a panel is displayed with all problems available for peer grading.', - default=USE_FOR_SINGLE_LOCATION, + default=False, scope=Scope.settings ) link_to_location = String( display_name="Link to Problem Location", help='The location of the problem being graded. Only used when "Show Single Problem" is True.', - default=LINK_TO_LOCATION, + default="", scope=Scope.settings ) - is_graded = Boolean( + graded = Boolean( display_name="Graded", help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.', - default=IS_GRADED, + default=False, scope=Scope.settings ) due_date = Date( @@ -56,12 +51,6 @@ class PeerGradingFields(object): default=None, scope=Scope.settings ) - max_grade = Integer( - help="The maximum grade that a student can receive for this problem.", - default=MAX_SCORE, - scope=Scope.settings, - values={"min": 0} - ) student_data_for_location = Dict( help="Student data for a given peer grading problem.", scope=Scope.user_state @@ -136,10 +125,6 @@ def __init__(self, *args, **kwargs): if not self.ajax_url.endswith("/"): self.ajax_url = self.ajax_url + "/" - # Integer could return None, so keep this check. - if not isinstance(self.max_grade, int): - raise TypeError("max_grade needs to be an integer.") - def closed(self): return self._closed(self.timeinfo) @@ -232,7 +217,7 @@ def get_score(self): 'score': score, 'total': max_score, } - if not self.use_for_single_location or not self.is_graded: + if not self.use_for_single_location or not self.graded: return score_dict try: @@ -253,7 +238,7 @@ def get_score(self): self.student_data_for_location = response score = int(count_graded >= count_required and count_graded > 0) * float(weight) - total = self.max_grade * float(weight) + total = float(weight) score_dict['score'] = score score_dict['total'] = total @@ -266,8 +251,8 @@ def max_score(self): randomization, and 5/7 on another ''' max_grade = None - if self.use_for_single_location and self.is_graded: - max_grade = self.max_grade + if self.use_for_single_location and self.graded: + max_grade = self.weight return max_grade def get_next_submission(self, data): @@ -634,9 +619,13 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): #Specify whether or not to pass in open ended interface needs_open_ended_interface = True + metadata_translations = { + 'is_graded': 'graded', + 'attempts': 'max_attempts', + } + @property def non_editable_metadata_fields(self): non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields - non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string, - PeerGradingFields.max_grade]) + non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string]) return non_editable_fields From 84c4b7f1390721704ba0269c5e20af12fa4cf91f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 15:08:52 -0400 Subject: [PATCH 10/13] Test fixes --- cms/djangoapps/contentstore/tests/test_contentstore.py | 2 +- .../combined_open_ended_modulev1.py | 4 ++-- common/lib/xmodule/xmodule/tests/test_combined_open_ended.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 500db414f4d2..3e1b046a9865 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -134,7 +134,7 @@ def test_advanced_components_in_edit_unit(self): self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha', 'Word cloud', 'Annotation', - 'Open Ended Grading', + 'Open Response Assessment', 'Peer Grading Interface']) def test_advanced_components_require_two_clicks(self): diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index fbc846ecd361..4d406ddd830a 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -614,14 +614,14 @@ def reset(self, data): return self.out_of_sync_error(data) self.student_attempts +=1 - if self.student_attempts >= self.attempts: + if self.student_attempts >= self.max_attempts: return { 'success': False, # This is a student_facing_error 'error': ( 'You have attempted this question {0} times. ' 'You are only allowed to attempt it {1} times.' - ).format(self.student_attempts, self.attempts) + ).format(self.student_attempts, self.max_attempts) } self.state = self.INITIAL self.ready_to_reset = False diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index af1b6aa12b94..0a14608edc09 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -335,7 +335,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): 's3_interface': test_util_open_ended.S3_INTERFACE, 'open_ended_grading_interface': test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE, 'skip_basic_checks': False, - 'is_graded': True, + 'graded': True, } oeparam = etree.XML(''' From 0b991f9bbd2bc6d809527a31f4c7d3bc7548397d Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 15:19:13 -0400 Subject: [PATCH 11/13] Fix reset and score tests --- .../combined_open_ended_modulev1.py | 18 ++++++++++-------- .../xmodule/tests/test_combined_open_ended.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py index 4d406ddd830a..933eb0b5bb1f 100644 --- a/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py +++ b/common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py @@ -101,14 +101,14 @@ def __init__(self, system, location, definition, descriptor, # Allow reset is true if student has failed the criteria to move to the next child task self.ready_to_reset = instance_state.get('ready_to_reset', False) - self.max_attempts = self.instance_state.get('max_attempts', MAX_ATTEMPTS) - self.is_scored = self.instance_state.get('graded', IS_SCORED) in TRUE_DICT - self.accept_file_upload = self.instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT - self.skip_basic_checks = self.instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT + self.max_attempts = instance_state.get('max_attempts', MAX_ATTEMPTS) + self.is_scored = instance_state.get('graded', IS_SCORED) in TRUE_DICT + self.accept_file_upload = instance_state.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT + self.skip_basic_checks = instance_state.get('skip_spelling_checks', SKIP_BASIC_CHECKS) in TRUE_DICT - due_date = self.instance_state.get('due', None) + due_date = instance_state.get('due', None) - grace_period_string = self.instance_state.get('graceperiod', None) + grace_period_string = instance_state.get('graceperiod', None) try: self.timeinfo = TimeInfo(due_date, grace_period_string) except Exception: @@ -613,8 +613,9 @@ def reset(self, data): if not self.ready_to_reset: return self.out_of_sync_error(data) - self.student_attempts +=1 - if self.student_attempts >= self.max_attempts: + if self.student_attempts >= self.max_attempts-1: + if self.student_attempts==self.max_attempts-1: + self.student_attempts +=1 return { 'success': False, # This is a student_facing_error @@ -623,6 +624,7 @@ def reset(self, data): 'You are only allowed to attempt it {1} times.' ).format(self.student_attempts, self.max_attempts) } + self.student_attempts +=1 self.state = self.INITIAL self.ready_to_reset = False for i in xrange(0, len(self.task_xml)): diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py index 0a14608edc09..7d369a8429c4 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -453,7 +453,7 @@ def test_alternate_orderings(self): self.assertFalse(changed) def test_get_score_realistic(self): - instance_state = r"""{"ready_to_reset": false, "skip_spelling_checks": true, "current_task_number": 1, "weight": 5.0, "graceperiod": "1 day 12 hours 59 minutes 59 seconds", "is_graded": "True", "task_states": ["{\"child_created\": false, \"child_attempts\": 4, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"\", \"post_assessment\": \"[3]\", \"score\": 3}], \"max_score\": 3, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"{\\\"submission_id\\\": 3098, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3235, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"{\\\"submission_id\\\": 3099, \\\"score\\\": 3, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"to replicate the experiment , the procedure would require more detail . one piece of information that is omitted is the amount of vinegar used in the experiment . it is also important to know what temperature the experiment was kept at during the hours . finally , the procedure needs to include details about the experiment , for example if the whole sample must be submerged .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3237, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality3\\\"}\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"{\\\"submission_id\\\": 3100, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"e the mass of four different samples . pour vinegar in each of four separate , but identical , containers . place a sample of one material into one container and label . repeat with remaining samples , placing a single sample into a single container . after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . \\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3239, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"\", \"post_assessment\": \"{\\\"submission_id\\\": 3101, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"invalid essay .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3241, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}], \"max_score\": 3, \"child_state\": \"done\"}"], "attempts": "10000", "student_attempts": 0, "due": null, "state": "done", "accept_file_upload": false, "display_name": "Science Question -- Machine Assessed"}""" + instance_state = r"""{"ready_to_reset": false, "skip_spelling_checks": true, "current_task_number": 1, "weight": 5.0, "graceperiod": "1 day 12 hours 59 minutes 59 seconds", "graded": "True", "task_states": ["{\"child_created\": false, \"child_attempts\": 4, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"[3]\", \"score\": 3}, {\"answer\": \"\", \"post_assessment\": \"[3]\", \"score\": 3}], \"max_score\": 3, \"child_state\": \"done\"}", "{\"child_created\": false, \"child_attempts\": 0, \"version\": 1, \"child_history\": [{\"answer\": \"The students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the group\\u2019s procedure, describe what additional information you would need in order to replicate the expe\", \"post_assessment\": \"{\\\"submission_id\\\": 3097, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: More grammar errors than average.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the groups procedure , describe what additional information you would need in order to replicate the expe\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3233, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"After 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\\r\\nStarting Mass (g)\\tEnding Mass (g)\\tDifference in Mass (g)\\r\\nMarble\\t 9.8\\t 9.4\\t\\u20130.4\\r\\nLimestone\\t10.4\\t 9.1\\t\\u20131.3\\r\\nWood\\t11.2\\t11.2\\t 0.0\\r\\nPlastic\\t 7.2\\t 7.1\\t\\u20130.1\\r\\nAfter reading the\", \"post_assessment\": \"{\\\"submission_id\\\": 3098, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . starting mass g ending mass g difference in mass g marble . . . limestone . . . wood . . . plastic . . . after reading the\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3235, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"To replicate the experiment, the procedure would require more detail. One piece of information that is omitted is the amount of vinegar used in the experiment. It is also important to know what temperature the experiment was kept at during the 24 hours. Finally, the procedure needs to include details about the experiment, for example if the whole sample must be submerged.\", \"post_assessment\": \"{\\\"submission_id\\\": 3099, \\\"score\\\": 3, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"to replicate the experiment , the procedure would require more detail . one piece of information that is omitted is the amount of vinegar used in the experiment . it is also important to know what temperature the experiment was kept at during the hours . finally , the procedure needs to include details about the experiment , for example if the whole sample must be submerged .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3237, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality3\\\"}\", \"score\": 3}, {\"answer\": \"e the mass of four different samples.\\r\\nPour vinegar in each of four separate, but identical, containers.\\r\\nPlace a sample of one material into one container and label. Repeat with remaining samples, placing a single sample into a single container.\\r\\nAfter 24 hours, remove the samples from the containers and rinse each sample with distilled water.\\r\\nAllow the samples to sit and dry for 30 minutes.\\r\\nDetermine the mass of each sample.\\r\\nThe students\\u2019 data are recorded in the table below.\\r\\n\", \"post_assessment\": \"{\\\"submission_id\\\": 3100, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"e the mass of four different samples . pour vinegar in each of four separate , but identical , containers . place a sample of one material into one container and label . repeat with remaining samples , placing a single sample into a single container . after hours , remove the samples from the containers and rinse each sample with distilled water . allow the samples to sit and dry for minutes . determine the mass of each sample . the students data are recorded in the table below . \\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3239, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}, {\"answer\": \"\", \"post_assessment\": \"{\\\"submission_id\\\": 3101, \\\"score\\\": 0, \\\"feedback\\\": \\\"{\\\\\\\"spelling\\\\\\\": \\\\\\\"Spelling: Ok.\\\\\\\", \\\\\\\"grammar\\\\\\\": \\\\\\\"Grammar: Ok.\\\\\\\", \\\\\\\"markup-text\\\\\\\": \\\\\\\"invalid essay .\\\\\\\"}\\\", \\\"success\\\": true, \\\"grader_id\\\": 3241, \\\"grader_type\\\": \\\"ML\\\", \\\"rubric_scores_complete\\\": true, \\\"rubric_xml\\\": \\\"Response Quality0\\\"}\", \"score\": 0}], \"max_score\": 3, \"child_state\": \"done\"}"], "attempts": "10000", "student_attempts": 0, "due": null, "state": "done", "accept_file_upload": false, "display_name": "Science Question -- Machine Assessed"}""" instance_state = json.loads(instance_state) rubric = """ From 2a4976cf8a46046fe411da7c35cb2c7ce01532ef Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 15:36:42 -0400 Subject: [PATCH 12/13] Cleanup how peer grading handles due date --- .../xmodule/xmodule/peer_grading_module.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index 8f5f93318aa0..ff5d41dd8a4c 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -42,7 +42,7 @@ class PeerGradingFields(object): default=False, scope=Scope.settings ) - due_date = Date( + due = Date( help="Due date that should be displayed.", default=None, scope=Scope.settings) @@ -100,25 +100,25 @@ def __init__(self, *args, **kwargs): if self.use_for_single_location: try: self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) - except: + except Exception: log.error("Linked location {0} for peer grading module {1} does not exist".format( self.link_to_location, self.location)) raise - due_date = self.linked_problem._model_data.get('peer_grading_due', None) + due_date = self.linked_problem._model_data.get('due', None) if due_date: self._model_data['due'] = due_date try: - self.timeinfo = TimeInfo(self.due_date, self.grace_period_string) - except: - log.error("Error parsing due date information in location {0}".format(location)) + self.timeinfo = TimeInfo(self.due, self.grace_period_string) + except Exception: + log.error("Error parsing due date information in location {0}".format(self.location)) raise self.display_due_date = self.timeinfo.display_due_date try: self.student_data_for_location = json.loads(self.student_data_for_location) - except: + except Exception: pass self.ajax_url = self.system.ajax_url @@ -532,7 +532,7 @@ def _find_corresponding_module_for_location(location): problem_location = problem['location'] descriptor = _find_corresponding_module_for_location(problem_location) if descriptor: - problem['due'] = descriptor._model_data.get('peer_grading_due', None) + problem['due'] = descriptor._model_data.get('due', None) grace_period_string = descriptor._model_data.get('graceperiod', None) try: problem_timeinfo = TimeInfo(problem['due'], grace_period_string) @@ -622,10 +622,11 @@ class PeerGradingDescriptor(PeerGradingFields, RawDescriptor): metadata_translations = { 'is_graded': 'graded', 'attempts': 'max_attempts', + 'due_data' : 'due' } @property def non_editable_metadata_fields(self): non_editable_fields = super(PeerGradingDescriptor, self).non_editable_metadata_fields - non_editable_fields.extend([PeerGradingFields.due_date, PeerGradingFields.grace_period_string]) + non_editable_fields.extend([PeerGradingFields.due, PeerGradingFields.grace_period_string]) return non_editable_fields From a478fa9ff752bd5296aa1fd359442dd9c0791937 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 18 Jul 2013 16:56:38 -0400 Subject: [PATCH 13/13] Change error class --- common/lib/xmodule/xmodule/peer_grading_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/peer_grading_module.py b/common/lib/xmodule/xmodule/peer_grading_module.py index ff5d41dd8a4c..09cac9a6b45a 100644 --- a/common/lib/xmodule/xmodule/peer_grading_module.py +++ b/common/lib/xmodule/xmodule/peer_grading_module.py @@ -9,6 +9,7 @@ from .x_module import XModule from xmodule.raw_module import RawDescriptor from xmodule.modulestore.django import modulestore +from xmodule.modulestore.exceptions import ItemNotFoundError from .timeinfo import TimeInfo from xblock.core import Dict, String, Scope, Boolean, Integer, Float from xmodule.fields import Date @@ -100,7 +101,7 @@ def __init__(self, *args, **kwargs): if self.use_for_single_location: try: self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location) - except Exception: + except ItemNotFoundError: log.error("Linked location {0} for peer grading module {1} does not exist".format( self.link_to_location, self.location)) raise