From dc3dd41aa77d50d180c451e52b2761fc42c91c8a Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 24 Jan 2013 08:58:24 -0500 Subject: [PATCH 001/153] Update tests to cover the peer grading service --- lms/djangoapps/open_ended_grading/tests.py | 83 +++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py index 0c4376a44b..bd2b3c6695 100644 --- a/lms/djangoapps/open_ended_grading/tests.py +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -6,6 +6,7 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open from django.test import TestCase from open_ended_grading import staff_grading_service +from open_ended_grading import peer_grading_service from django.core.urlresolvers import reverse from django.contrib.auth.models import Group @@ -19,7 +20,6 @@ import json from override_settings import override_settings -_mock_service = staff_grading_service.MockStaffGradingService() @override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) class TestStaffGradingService(ct.PageLoader): @@ -110,3 +110,84 @@ class TestStaffGradingService(ct.PageLoader): d = json.loads(r.content) self.assertTrue(d['success'], str(d)) self.assertIsNotNone(d['problem_list']) + + +@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE) +class TestPeerGradingService(ct.PageLoader): + ''' + Check that staff grading service proxy works. Basically just checking the + access control and error handling logic -- all the actual work is on the + backend. + ''' + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + + self.student = 'view@test.com' + self.instructor = 'view2@test.com' + self.password = 'foo' + self.location = 'TestLocation' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.instructor, self.password) + self.activate_user(self.student) + self.activate_user(self.instructor) + + self.course_id = "edX/toy/2012_Fall" + self.toy = modulestore().get_course(self.course_id) + + self.mock_service = peer_grading_service.peer_grading_service() + + self.logout() + + def test_get_next_submission_success(self): + self.login(self.student, self.password) + + url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id}) + data = {'location': self.location} + + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success']) + self.assertIsNotNone(d['submission_id']) + self.assertIsNotNone(d['prompt']) + self.assertIsNotNone(d['submission_key']) + self.assertIsNotNone(d['max_score']) + + def test_get_next_submission_missing_location(self): + self.login(self.student, self.password) + url = reverse('peer_grading_get_next_submission', kwargs={'course_id': self.course_id}) + data = {} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertFalse(d['success']) + self.assertEqual(d['error'], "Missing required keys: location") + + def test_save_grade_success(self): + self.login(self.student, self.password) + url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id}) + data = {'location': self.location, + 'submission_id': '1', + 'submission_key': 'fake key', + 'score': '2', + 'feedback': 'This is feedback'} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success']) + + def test_save_grade_missing_keys(self): + self.login(self.student, self.password) + url = reverse('peer_grading_save_grade', kwargs={'course_id': self.course_id}) + data = {} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertFalse(d['success']) + self.assertTrue(d['error'].find('Missing required keys:') > -1) + + def test_is_calibrated(self): + self.login(self.student, self.password) + url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id}) + data = {'location': self.location} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success']) + self.assertTrue('calibrated' in d) + From 210cdf870eb5e2d8d27c10c5676cf87968fbc97f Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 24 Jan 2013 09:41:24 -0500 Subject: [PATCH 002/153] Updates to the mock peer grading service and new tests. --- .../peer_grading_service.py | 15 ++++- lms/djangoapps/open_ended_grading/tests.py | 62 ++++++++++++++++++- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py index 8e0f8cbbaa..0ae4cc4534 100644 --- a/lms/djangoapps/open_ended_grading/peer_grading_service.py +++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py @@ -29,6 +29,15 @@ This is a mock peer grading service that can be used for unit tests without making actual service calls to the grading controller """ class MockPeerGradingService(object): + # TODO: get this rubric parsed and working + rubric = """ + + Description + + + + """ + def get_next_submission(self, problem_location, grader_id): return json.dumps({'success': True, 'submission_id':1, @@ -56,15 +65,15 @@ class MockPeerGradingService(object): def save_calibration_essay(self, problem_location, grader_id, calibration_essay_id, submission_key, score, feedback): - return {'success': True, 'actual_score': 2} + return json.dumps({'success': True, 'actual_score': 2}) def get_problem_list(self, course_id, grader_id): return json.dumps({'success': True, 'problem_list': [ json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo1', - 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5}), + 'problem_name': "Problem 1", 'num_graded': 3, 'num_pending': 5, 'num_required': 7}), json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2', - 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5}) + 'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5, 'num_required': 8}) ]}) class PeerGradingService(GradingService): diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py index bd2b3c6695..d1670a1b26 100644 --- a/lms/djangoapps/open_ended_grading/tests.py +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -18,6 +18,8 @@ from nose import SkipTest from mock import patch, Mock import json +import logging +log = logging.getLogger(__name__) from override_settings import override_settings @@ -182,7 +184,7 @@ class TestPeerGradingService(ct.PageLoader): self.assertFalse(d['success']) self.assertTrue(d['error'].find('Missing required keys:') > -1) - def test_is_calibrated(self): + def test_is_calibrated_success(self): self.login(self.student, self.password) url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id}) data = {'location': self.location} @@ -191,3 +193,61 @@ class TestPeerGradingService(ct.PageLoader): self.assertTrue(d['success']) self.assertTrue('calibrated' in d) + def test_is_calibrated_failure(self): + self.login(self.student, self.password) + url = reverse('peer_grading_is_student_calibrated', kwargs={'course_id': self.course_id}) + data = {} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertFalse(d['success']) + self.assertFalse('calibrated' in d) + + def test_show_calibration_essay_success(self): + self.login(self.student, self.password) + + url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id}) + data = {'location': self.location} + + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success']) + self.assertIsNotNone(d['submission_id']) + self.assertIsNotNone(d['prompt']) + self.assertIsNotNone(d['submission_key']) + self.assertIsNotNone(d['max_score']) + + def test_show_calibration_essay_missing_key(self): + self.login(self.student, self.password) + + url = reverse('peer_grading_show_calibration_essay', kwargs={'course_id': self.course_id}) + data = {} + + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + + self.assertFalse(d['success']) + self.assertEqual(d['error'], "Missing required keys: location") + + def test_save_calibration_essay_success(self): + self.login(self.student, self.password) + url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id}) + data = {'location': self.location, + 'submission_id': '1', + 'submission_key': 'fake key', + 'score': '2', + 'feedback': 'This is feedback'} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertTrue(d['success']) + self.assertTrue('actual_score' in d) + + def test_save_calibration_essay_missing_keys(self): + self.login(self.student, self.password) + url = reverse('peer_grading_save_calibration_essay', kwargs={'course_id': self.course_id}) + data = {} + r = self.check_for_post_code(200, url, data) + d = json.loads(r.content) + self.assertFalse(d['success']) + self.assertTrue(d['error'].find('Missing required keys:') > -1) + self.assertFalse('actual_score' in d) + From 1f8db18903efe30a21816de5543abc134d6ecd93 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 24 Jan 2013 12:36:48 -0500 Subject: [PATCH 003/153] New tests for open ended module --- .../xmodule/tests/test_combined_open_ended.py | 73 +++++++++++++++++++ .../xmodule/tests/test_self_assessment.py | 59 ++++++++------- 2 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 common/lib/xmodule/xmodule/tests/test_combined_open_ended.py diff --git a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py new file mode 100644 index 0000000000..12ad862a43 --- /dev/null +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -0,0 +1,73 @@ +import json +from mock import Mock +import unittest + +from xmodule.openendedchild import OpenEndedChild +from xmodule.modulestore import Location +from lxml import etree + +from . import test_system + +class OpenEndedChildTest(unittest.TestCase): + location = Location(["i4x", "edX", "sa_test", "selfassessment", + "SampleQuestion"]) + + metadata = json.dumps({'attempts': '10'}) + prompt = etree.XML("This is a question prompt") + rubric = ''' + + Response Quality + + + ''' + max_score = 4 + + static_data = { + 'max_attempts': 20, + 'prompt': prompt, + 'rubric': rubric, + 'max_score': max_score, + } + definition = Mock() + descriptor = Mock() + + + def test_latest_answer_empty(self): + openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, self.metadata) + answer = openendedchild.latest_answer() + self.assertEqual(answer, "") + + def test_latest_answer_nonempty(self): + metadata = json.dumps({ 'attempts': 10, + 'history': [{'answer': "Two"}, {'answer': "Three"}]}) + openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, metadata) + answer = openendedchild.latest_answer() + self.assertEqual(answer, "Three") + + def test_latest_score_empty(self): + openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, self.metadata) + answer = openendedchild.latest_score() + self.assertEqual(answer, None) + + + def test_latest_score_nonempty(self): + metadata = json.dumps({ 'attempts': 10, + 'history': [{'score': 3}, {'score': 2}]}) + openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, metadata) + answer = openendedchild.latest_score() + self.assertEqual(answer, 2) + + + def test_new_history_entry(self): + openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, self.metadata) + new_answer = "New Answer" + openendedchild.new_history_entry(new_answer) + answer = openendedchild.latest_answer() + self.assertEqual(answer, new_answer) + + #def test_record_latest_score(self): diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py index 565483c586..a11facb05b 100644 --- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py +++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py @@ -10,8 +10,16 @@ from . import test_system class SelfAssessmentTest(unittest.TestCase): - definition = {'rubric': 'A rubric', - 'prompt': 'Who?', + rubric = ''' + + Response Quality + + + ''' + + prompt = etree.XML("This is sample prompt text.") + definition = {'rubric': rubric, + 'prompt': prompt, 'submitmessage': 'Shall we submit now?', 'hintprompt': 'Consider this...', } @@ -23,48 +31,47 @@ class SelfAssessmentTest(unittest.TestCase): descriptor = Mock() - def test_import(self): + def setUp(self): state = json.dumps({'student_answers': ["Answer 1", "answer 2", "answer 3"], 'scores': [0, 1], 'hints': ['o hai'], 'state': SelfAssessmentModule.INITIAL, 'attempts': 2}) - rubric = ''' - - Response Quality - - - ''' - - prompt = etree.XML("Text") static_data = { 'max_attempts': 10, - 'rubric': etree.XML(rubric), - 'prompt': prompt, + 'rubric': etree.XML(self.rubric), + 'prompt': self.prompt, 'max_score': 1 } - module = SelfAssessmentModule(test_system, self.location, + self.module = SelfAssessmentModule(test_system, self.location, self.definition, self.descriptor, static_data, state, metadata=self.metadata) - self.assertEqual(module.get_score()['score'], 0) + def test_get_html(self): + html = self.module.get_html(test_system) + self.assertTrue(html.find("This is sample prompt text") != -1) + + def test_self_assessment_flow(self): + + self.assertEqual(self.module.get_score()['score'], 0) - module.save_answer({'student_answer': "I am an answer"}, test_system) - self.assertEqual(module.state, module.ASSESSING) + self.module.save_answer({'student_answer': "I am an answer"}, test_system) + self.assertEqual(self.module.state, self.module.ASSESSING) - module.save_assessment({'assessment': '0'}, test_system) - self.assertEqual(module.state, module.POST_ASSESSMENT) - module.save_hint({'hint': 'this is a hint'}, test_system) - self.assertEqual(module.state, module.DONE) + self.module.save_assessment({'assessment': '0'}, test_system) + self.assertEqual(self.module.state, self.module.POST_ASSESSMENT) + self.module.save_hint({'hint': 'this is a hint'}, test_system) + self.assertEqual(self.module.state, self.module.DONE) - d = module.reset({}) + d = self.module.reset({}) self.assertTrue(d['success']) - self.assertEqual(module.state, module.INITIAL) + self.assertEqual(self.module.state, self.module.INITIAL) # if we now assess as right, skip the REQUEST_HINT state - module.save_answer({'student_answer': 'answer 4'}, test_system) - module.save_assessment({'assessment': '1'}, test_system) - self.assertEqual(module.state, module.DONE) + self.module.save_answer({'student_answer': 'answer 4'}, test_system) + self.module.save_assessment({'assessment': '1'}, test_system) + self.assertEqual(self.module.state, self.module.DONE) + From 37c7b470173c5bebab64c1c79a362f7e25f41092 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 24 Jan 2013 14:02:24 -0500 Subject: [PATCH 004/153] Update comments and add new OpenEndedChild tests --- common/lib/xmodule/xmodule/openendedchild.py | 4 +- .../xmodule/tests/test_combined_open_ended.py | 101 +++++++++++++----- 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/openendedchild.py b/common/lib/xmodule/xmodule/openendedchild.py index 88fed61c6d..8d3eb3e617 100644 --- a/common/lib/xmodule/xmodule/openendedchild.py +++ b/common/lib/xmodule/xmodule/openendedchild.py @@ -112,7 +112,7 @@ class OpenEndedChild(object): pass def latest_answer(self): - """None if not available""" + """Empty string if not available""" if not self.history: return "" return self.history[-1].get('answer', "") @@ -124,7 +124,7 @@ class OpenEndedChild(object): return self.history[-1].get('score') def latest_post_assessment(self, system): - """None if not available""" + """Empty string if not available""" if not self.history: return "" return self.history[-1].get('post_assessment', "") 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 12ad862a43..c53317fe1f 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -31,43 +31,94 @@ class OpenEndedChildTest(unittest.TestCase): definition = Mock() descriptor = Mock() + def setUp(self): + self.openendedchild = OpenEndedChild(test_system, self.location, + self.definition, self.descriptor, self.static_data, self.metadata) + def test_latest_answer_empty(self): - openendedchild = OpenEndedChild(test_system, self.location, - self.definition, self.descriptor, self.static_data, self.metadata) - answer = openendedchild.latest_answer() + answer = self.openendedchild.latest_answer() self.assertEqual(answer, "") - def test_latest_answer_nonempty(self): - metadata = json.dumps({ 'attempts': 10, - 'history': [{'answer': "Two"}, {'answer': "Three"}]}) - openendedchild = OpenEndedChild(test_system, self.location, - self.definition, self.descriptor, self.static_data, metadata) - answer = openendedchild.latest_answer() - self.assertEqual(answer, "Three") def test_latest_score_empty(self): - openendedchild = OpenEndedChild(test_system, self.location, - self.definition, self.descriptor, self.static_data, self.metadata) - answer = openendedchild.latest_score() + answer = self.openendedchild.latest_score() self.assertEqual(answer, None) - def test_latest_score_nonempty(self): - metadata = json.dumps({ 'attempts': 10, - 'history': [{'score': 3}, {'score': 2}]}) - openendedchild = OpenEndedChild(test_system, self.location, - self.definition, self.descriptor, self.static_data, metadata) - answer = openendedchild.latest_score() - self.assertEqual(answer, 2) + def test_latest_post_assessment_empty(self): + answer = self.openendedchild.latest_post_assessment(test_system) + self.assertEqual(answer, "") def test_new_history_entry(self): - openendedchild = OpenEndedChild(test_system, self.location, - self.definition, self.descriptor, self.static_data, self.metadata) new_answer = "New Answer" - openendedchild.new_history_entry(new_answer) - answer = openendedchild.latest_answer() + self.openendedchild.new_history_entry(new_answer) + answer = self.openendedchild.latest_answer() self.assertEqual(answer, new_answer) - #def test_record_latest_score(self): + new_answer = "Newer Answer" + self.openendedchild.new_history_entry(new_answer) + answer = self.openendedchild.latest_answer() + self.assertEqual(new_answer, answer) + + def test_record_latest_score(self): + new_answer = "New Answer" + self.openendedchild.new_history_entry(new_answer) + new_score = 3 + self.openendedchild.record_latest_score(new_score) + score = self.openendedchild.latest_score() + self.assertEqual(score, 3) + + new_score = 4 + self.openendedchild.new_history_entry(new_answer) + self.openendedchild.record_latest_score(new_score) + score = self.openendedchild.latest_score() + self.assertEqual(score, 4) + + + def test_record_latest_post_assessment(self): + new_answer = "New Answer" + self.openendedchild.new_history_entry(new_answer) + + post_assessment = "Post assessment" + self.openendedchild.record_latest_post_assessment(post_assessment) + self.assertEqual(post_assessment, + self.openendedchild.latest_post_assessment(test_system)) + + def test_get_score(self): + new_answer = "New Answer" + self.openendedchild.new_history_entry(new_answer) + + score = self.openendedchild.get_score() + self.assertEqual(score['score'], 0) + self.assertEqual(score['total'], self.static_data['max_score']) + + new_score = 4 + self.openendedchild.new_history_entry(new_answer) + self.openendedchild.record_latest_score(new_score) + score = self.openendedchild.get_score() + self.assertEqual(score['score'], new_score) + self.assertEqual(score['total'], self.static_data['max_score']) + + + def test_reset(self): + self.openendedchild.reset(test_system) + state = json.loads(self.openendedchild.get_instance_state()) + self.assertEqual(state['state'], OpenEndedChild.INITIAL) + + + def test_is_last_response_correct(self): + new_answer = "New Answer" + self.openendedchild.new_history_entry(new_answer) + self.openendedchild.record_latest_score(self.static_data['max_score']) + self.assertEqual(self.openendedchild.is_last_response_correct(), + 'correct') + + self.openendedchild.new_history_entry(new_answer) + self.openendedchild.record_latest_score(0) + self.assertEqual(self.openendedchild.is_last_response_correct(), + 'incorrect') + + + From bb60dde45e5dd03d8d90c47c03d06e518a6fc2ce Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Thu, 24 Jan 2013 17:09:19 -0500 Subject: [PATCH 005/153] Add OpenEndedModule tests --- .../xmodule/tests/test_combined_open_ended.py | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) 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 c53317fe1f..7ae17f6a4f 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -1,8 +1,9 @@ import json -from mock import Mock +from mock import Mock, MagicMock import unittest from xmodule.openendedchild import OpenEndedChild +from xmodule.open_ended_module import OpenEndedModule from xmodule.modulestore import Location from lxml import etree @@ -120,5 +121,59 @@ class OpenEndedChildTest(unittest.TestCase): self.assertEqual(self.openendedchild.is_last_response_correct(), 'incorrect') +class OpenEndedModuleTest(unittest.TestCase): + location = Location(["i4x", "edX", "sa_test", "selfassessment", + "SampleQuestion"]) + + metadata = json.dumps({'attempts': '10'}) + prompt = etree.XML("This is a question prompt") + rubric = etree.XML(''' + + Response Quality + + + ''') + max_score = 4 + + static_data = { + 'max_attempts': 20, + 'prompt': prompt, + 'rubric': rubric, + 'max_score': max_score, + } + + oeparam = etree.XML(''' + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + ''') + definition = {'oeparam': oeparam} + descriptor = Mock() + + def setUp(self): + test_system.location = self.location + self.mock_xqueue = MagicMock() + self.mock_xqueue.send_to_queue.return_value=(None, "Message") + test_system.xqueue = {'interface':self.mock_xqueue, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 1} + self.openendedmodule = OpenEndedModule(test_system, self.location, + self.definition, self.descriptor, self.static_data, self.metadata) + + def test_message_post(self): + get = {'feedback': 'feedback text', + 'submission_id': '1', + 'grader_id': '1', + 'score': 3} + + result = self.openendedmodule.message_post(get, test_system) + self.assertTrue(result['success']) + state = json.loads(self.openendedmodule.get_instance_state()) + self.assertIsNotNone(state['state'], OpenEndedModule.DONE) + + def test_send_to_grader(self): + result = self.openendedmodule.send_to_grader("This is a student submission", test_system) + self.assertTrue(result) + From bb521a20b7f33743bd52a512573a29d0580aaf74 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 25 Jan 2013 10:19:09 -0500 Subject: [PATCH 006/153] fix a bug caught with testing and update some tests (the last couple are still broken, though) --- .../lib/xmodule/xmodule/open_ended_module.py | 6 +- .../xmodule/tests/test_combined_open_ended.py | 69 ++++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 0eaad34bad..0b7523f984 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -257,7 +257,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild): """ new_score_msg = self._parse_score_msg(score_msg, system) if not new_score_msg['valid']: - score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' + new_score_msg['feedback'] = 'Invalid grader reply. Please contact the course staff.' self.record_latest_score(new_score_msg['score']) self.record_latest_post_assessment(score_msg) @@ -404,6 +404,10 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'score': Numeric value (floating point is okay) to assign to answer 'msg': grader_msg 'feedback' : feedback from grader + 'grader_type': what type of grader resulted in this score + 'grader_id': id of the grader + 'submission_id' : id of the submission + 'success': whether or not this submission was successful } Returns (valid_score_msg, correct, score, msg): 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 7ae17f6a4f..18176d0e12 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -1,11 +1,13 @@ import json -from mock import Mock, MagicMock +from mock import Mock, MagicMock, ANY import unittest from xmodule.openendedchild import OpenEndedChild from xmodule.open_ended_module import OpenEndedModule from xmodule.modulestore import Location from lxml import etree +import capa.xqueue_interface as xqueue_interface +from datetime import datetime from . import test_system @@ -165,15 +167,78 @@ class OpenEndedModuleTest(unittest.TestCase): 'submission_id': '1', 'grader_id': '1', 'score': 3} + qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) + student_info = {'anonymous_student_id': test_system.anonymous_student_id, + 'submission_time': qtime} + contents = { + 'feedback': get['feedback'], + 'submission_id': int(get['submission_id']), + 'grader_id': int(get['grader_id']), + 'score': get['score'], + 'student_info': json.dumps(student_info) + } result = self.openendedmodule.message_post(get, test_system) self.assertTrue(result['success']) + # make sure it's actually sending something to the queue + self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY) + state = json.loads(self.openendedmodule.get_instance_state()) self.assertIsNotNone(state['state'], OpenEndedModule.DONE) def test_send_to_grader(self): - result = self.openendedmodule.send_to_grader("This is a student submission", test_system) + submission = "This is a student submission" + qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat) + student_info = {'anonymous_student_id': test_system.anonymous_student_id, + 'submission_time': qtime} + contents = self.openendedmodule.payload.copy() + contents.update({ + 'student_info': json.dumps(student_info), + 'student_response': submission, + 'max_score': self.max_score + }) + result = self.openendedmodule.send_to_grader(submission, test_system) self.assertTrue(result) + self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY) + + def update_score_single(self): + self.openendedmodule.new_history_entry("New Entry") + score_msg = { + 'correct': True, + 'score': 4, + 'msg' : 'Grader Message', + 'feedback': "Grader Feedback" + } + get = {'queuekey': "abcd", + 'xqueue_body': score_msg} + self.openendedmodule.update_score(get, test_system) + + def update_score_single(self): + self.openendedmodule.new_history_entry("New Entry") + feedback = { + "success": True, + "feedback": "Grader Feedback" + } + score_msg = { + 'correct': True, + 'score': 4, + 'msg' : 'Grader Message', + 'feedback': feedback, + 'grader_type': 'IN', + 'grader_id': '1', + 'submission_id': '1', + 'success': True + } + get = {'queuekey': "abcd", + 'xqueue_body': json.dumps(score_msg)} + self.openendedmodule.update_score(get, test_system) + def test_update_score(self): + self.update_score_single() + score = self.openendedmodule.latest_score() + self.assertEqual(score, 4) + + def test_latest_post_assessment(self): + self.update_score_single() From 71d27def0d28f41eb2283f848dce22defb47e292 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 25 Jan 2013 11:18:30 -0500 Subject: [PATCH 007/153] Fix tests and add new documentation --- .../lib/xmodule/xmodule/open_ended_module.py | 3 +++ .../xmodule/tests/test_combined_open_ended.py | 27 ++++++++++++++----- .../xmodule/tests/test_self_assessment.py | 2 +- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/common/lib/xmodule/xmodule/open_ended_module.py b/common/lib/xmodule/xmodule/open_ended_module.py index 7c3293730c..f32e0b26ff 100644 --- a/common/lib/xmodule/xmodule/open_ended_module.py +++ b/common/lib/xmodule/xmodule/open_ended_module.py @@ -410,6 +410,9 @@ class OpenEndedModule(openendedchild.OpenEndedChild): 'grader_id': id of the grader 'submission_id' : id of the submission 'success': whether or not this submission was successful + 'rubric_scores': a list of rubric scores + 'rubric_scores_complete': boolean if rubric scores are complete + 'rubric_xml': the xml of the rubric in string format } Returns (valid_score_msg, correct, score, msg): 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 138424f021..847ac108dc 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -10,6 +10,13 @@ import capa.xqueue_interface as xqueue_interface from datetime import datetime from . import test_system +""" +Tests for the various pieces of the CombinedOpenEndedGrading system + +OpenEndedChild +OpenEndedModule + +""" class OpenEndedChildTest(unittest.TestCase): location = Location(["i4x", "edX", "sa_test", "selfassessment", @@ -130,12 +137,12 @@ class OpenEndedModuleTest(unittest.TestCase): metadata = json.dumps({'attempts': '10'}) prompt = etree.XML("This is a question prompt") - rubric = etree.XML(''' + rubric = etree.XML(''' Response Quality - ''') + ''') max_score = 4 static_data = { @@ -225,22 +232,28 @@ class OpenEndedModuleTest(unittest.TestCase): 'correct': True, 'score': 4, 'msg' : 'Grader Message', - 'feedback': feedback, + 'feedback': json.dumps(feedback), 'grader_type': 'IN', 'grader_id': '1', 'submission_id': '1', - 'success': True + 'success': True, + 'rubric_scores': [0], + 'rubric_scores_complete': True, + 'rubric_xml': etree.tostring(self.rubric) } get = {'queuekey': "abcd", 'xqueue_body': json.dumps(score_msg)} self.openendedmodule.update_score(get, test_system) - + def test_latest_post_assessment(self): + self.update_score_single() + assessment = self.openendedmodule.latest_post_assessment(test_system) + self.assertFalse(assessment == '') + # check for errors + self.assertFalse('errors' in assessment) def test_update_score(self): self.update_score_single() score = self.openendedmodule.latest_score() self.assertEqual(score, 4) - def test_latest_post_assessment(self): - self.update_score_single() diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py index 55c198486c..9e138fef19 100644 --- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py +++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py @@ -52,7 +52,7 @@ class SelfAssessmentTest(unittest.TestCase): def test_get_html(self): html = self.module.get_html(test_system) - self.assertTrue(html.find("This is sample prompt text") != -1) + self.assertTrue("This is sample prompt text" in html) def test_self_assessment_flow(self): From a26006f7c32adbf68d4e2f766073ff941586c5e2 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 25 Jan 2013 15:09:01 -0500 Subject: [PATCH 008/153] Add new test for the CombinedOpenEndedModule --- .../xmodule/tests/test_combined_open_ended.py | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) 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 847ac108dc..3fb3cbe6d2 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -4,6 +4,8 @@ import unittest from xmodule.openendedchild import OpenEndedChild from xmodule.open_ended_module import OpenEndedModule +from xmodule.combined_open_ended_module import CombinedOpenEndedModule + from xmodule.modulestore import Location from lxml import etree import capa.xqueue_interface as xqueue_interface @@ -189,7 +191,7 @@ class OpenEndedModuleTest(unittest.TestCase): result = self.openendedmodule.message_post(get, test_system) self.assertTrue(result['success']) - # make sure it's actually sending something to the queue + # make sure it's actually sending something we want to the queue self.mock_xqueue.send_to_queue.assert_called_with(body = json.dumps(contents), header=ANY) state = json.loads(self.openendedmodule.get_instance_state()) @@ -257,3 +259,53 @@ class OpenEndedModuleTest(unittest.TestCase): score = self.openendedmodule.latest_score() self.assertEqual(score, 4) +class CombinedOpenEndedModuleTest(unittest.TestCase): + location = Location(["i4x", "edX", "open_ended", "combinedopenended", + "SampleQuestion"]) + + metadata = json.dumps({'attempts': '10'}) + prompt = "This is a question prompt" + rubric = ''' + + Response Quality + + + ''' + max_score = 4 + + static_data = json.dumps({ + 'max_attempts': 20, + 'prompt': prompt, + 'rubric': rubric, + 'max_score': max_score, + 'display_name': 'Name' + }) + + oeparam = etree.XML(''' + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + ''') + + task_xml = ''' + + + What hint about this problem would you give to someone? + + + Save Succcesful. Thanks for participating! + + + ''' + definition = {'prompt': etree.XML(prompt), 'rubric': etree.XML(rubric), 'task_xml': [task_xml]} + descriptor = Mock() + + def setUp(self): + self.combinedoe = CombinedOpenEndedModule(test_system, self.location, self.definition, self.descriptor, self.static_data, self.metadata) + + def test_get_tag_name(self): + name = self.combinedoe.get_tag_name("Tag") + self.assertEqual(name, "t") + From 9f6afcb606a77f8adad07e33e4275eae33023739 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Fri, 25 Jan 2013 16:33:45 -0500 Subject: [PATCH 009/153] Add some more tests --- .../xmodule/tests/test_combined_open_ended.py | 19 +++++++++++++++++-- 1 file changed, 17 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 3fb3cbe6d2..ba3dc10b4b 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -263,7 +263,6 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): location = Location(["i4x", "edX", "open_ended", "combinedopenended", "SampleQuestion"]) - metadata = json.dumps({'attempts': '10'}) prompt = "This is a question prompt" rubric = ''' @@ -273,6 +272,8 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ''' max_score = 4 + metadata = {'attempts': '10', 'max_score': max_score} + static_data = json.dumps({ 'max_attempts': 20, 'prompt': prompt, @@ -303,9 +304,23 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): descriptor = Mock() def setUp(self): - self.combinedoe = CombinedOpenEndedModule(test_system, self.location, self.definition, self.descriptor, self.static_data, self.metadata) + self.combinedoe = CombinedOpenEndedModule(test_system, self.location, self.definition, self.descriptor, self.static_data, metadata=self.metadata) def test_get_tag_name(self): name = self.combinedoe.get_tag_name("Tag") self.assertEqual(name, "t") + def test_get_last_response(self): + response_dict = self.combinedoe.get_last_response(0) + self.assertEqual(response_dict['type'], "selfassessment") + self.assertEqual(response_dict['max_score'], self.max_score) + self.assertEqual(response_dict['state'], CombinedOpenEndedModule.INITIAL) + + def test_update_task_states(self): + changed = self.combinedoe.update_task_states() + self.assertFalse(changed) + + # do something to change the state + + # check again + From 8d64bfdbdb910535fa41e130b7a2af9a7eb7aa66 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 28 Jan 2013 08:41:39 -0500 Subject: [PATCH 010/153] Update tests to handle rubric scores --- lms/djangoapps/open_ended_grading/peer_grading_service.py | 4 ++-- lms/djangoapps/open_ended_grading/tests.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/peer_grading_service.py b/lms/djangoapps/open_ended_grading/peer_grading_service.py index b850293255..b4f918397f 100644 --- a/lms/djangoapps/open_ended_grading/peer_grading_service.py +++ b/lms/djangoapps/open_ended_grading/peer_grading_service.py @@ -50,7 +50,7 @@ class MockPeerGradingService(object): 'max_score': 4}) def save_grade(self, location, grader_id, submission_id, - score, feedback, submission_key): + score, feedback, submission_key, rubric_scores): return json.dumps({'success': True}) def is_student_calibrated(self, problem_location, grader_id): @@ -66,7 +66,7 @@ class MockPeerGradingService(object): 'max_score': 4}) def save_calibration_essay(self, problem_location, grader_id, - calibration_essay_id, submission_key, score, feedback): + calibration_essay_id, submission_key, score, feedback, rubric_scores): return json.dumps({'success': True, 'actual_score': 2}) def get_problem_list(self, course_id, grader_id): diff --git a/lms/djangoapps/open_ended_grading/tests.py b/lms/djangoapps/open_ended_grading/tests.py index c1d192b6cc..57ea4f319c 100644 --- a/lms/djangoapps/open_ended_grading/tests.py +++ b/lms/djangoapps/open_ended_grading/tests.py @@ -171,7 +171,8 @@ class TestPeerGradingService(ct.PageLoader): 'submission_id': '1', 'submission_key': 'fake key', 'score': '2', - 'feedback': 'This is feedback'} + 'feedback': 'This is feedback', + 'rubric_scores[]': [1, 2]} r = self.check_for_post_code(200, url, data) d = json.loads(r.content) self.assertTrue(d['success']) @@ -236,7 +237,8 @@ class TestPeerGradingService(ct.PageLoader): 'submission_id': '1', 'submission_key': 'fake key', 'score': '2', - 'feedback': 'This is feedback'} + 'feedback': 'This is feedback', + 'rubric_scores[]': [1, 2]} r = self.check_for_post_code(200, url, data) d = json.loads(r.content) self.assertTrue(d['success']) From fe4dc7622ea1afb325d49cb04358e1357fc81957 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 28 Jan 2013 10:18:01 -0500 Subject: [PATCH 011/153] Updates to combined open ended tests --- .../xmodule/tests/test_combined_open_ended.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 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 ba3dc10b4b..b327d10340 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -290,7 +290,7 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ''') - task_xml = ''' + task_xml1 = ''' What hint about this problem would you give to someone? @@ -300,7 +300,15 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): ''' - definition = {'prompt': etree.XML(prompt), 'rubric': etree.XML(rubric), 'task_xml': [task_xml]} + task_xml2 = ''' + + + Enter essay here. + This is the answer. + {"grader_settings" : "ml_grading.conf", "problem_id" : "6.002x/Welcome/OETest"} + + ''' + definition = {'prompt': etree.XML(prompt), 'rubric': etree.XML(rubric), 'task_xml': [task_xml1, task_xml2]} descriptor = Mock() def setUp(self): @@ -320,7 +328,12 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): changed = self.combinedoe.update_task_states() self.assertFalse(changed) - # do something to change the state + #prev_context = self.combinedoe.get_context() + #new_state = {'state': CombinedOpenEndedModule.INITIAL, 'history': []} + ## force a task change + #task_state = self.combinedoe.overwrite_state(json.dumps(new_state)) - # check again + ## check again + #new_context = self.combinedoe.get_context() + #self.assertNotEqual(prev_context['state'], new_context['state']) From ffbda8a0be72c8add8b6210a6437d9f178148bdf Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Mon, 28 Jan 2013 11:52:15 -0500 Subject: [PATCH 012/153] Update test --- .../xmodule/tests/test_combined_open_ended.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 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 b327d10340..9485ed2a47 100644 --- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py +++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py @@ -328,12 +328,10 @@ class CombinedOpenEndedModuleTest(unittest.TestCase): changed = self.combinedoe.update_task_states() self.assertFalse(changed) - #prev_context = self.combinedoe.get_context() - #new_state = {'state': CombinedOpenEndedModule.INITIAL, 'history': []} - ## force a task change - #task_state = self.combinedoe.overwrite_state(json.dumps(new_state)) + current_task = self.combinedoe.current_task + current_task.change_state(CombinedOpenEndedModule.DONE) + changed = self.combinedoe.update_task_states() + + self.assertTrue(changed) - ## check again - #new_context = self.combinedoe.get_context() - #self.assertNotEqual(prev_context['state'], new_context['state']) From 3f4c45468fcb68301aad66fbc74ef5970b99f5cf Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Jan 2013 11:36:10 -0500 Subject: [PATCH 013/153] Fix courseware jasmine tests --- lms/static/coffee/spec/courseware_spec.coffee | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/lms/static/coffee/spec/courseware_spec.coffee b/lms/static/coffee/spec/courseware_spec.coffee index 3b40930b41..a51e64ec21 100644 --- a/lms/static/coffee/spec/courseware_spec.coffee +++ b/lms/static/coffee/spec/courseware_spec.coffee @@ -10,19 +10,6 @@ describe 'Courseware', -> Courseware.start() expect(Logger.bind).toHaveBeenCalled() - describe 'bind', -> - beforeEach -> - @courseware = new Courseware - setFixtures """ -
-
-
- """ - - it 'binds the content change event', -> - @courseware.bind() - expect($('.course-content .sequence')).toHandleWith 'contentChanged', @courseware.render - describe 'render', -> beforeEach -> jasmine.stubRequests() @@ -30,6 +17,7 @@ describe 'Courseware', -> spyOn(window, 'Histogram') spyOn(window, 'Problem') spyOn(window, 'Video') + spyOn(XModule, 'loadModules') setFixtures """
@@ -41,12 +29,8 @@ describe 'Courseware', -> """ @courseware.render() - it 'detect the video elements and convert them', -> - expect(window.Video).toHaveBeenCalledWith('1', '1.0:abc1234') - expect(window.Video).toHaveBeenCalledWith('2', '1.0:def5678') - - it 'detect the problem element and convert it', -> - expect(window.Problem).toHaveBeenCalledWith(3, 'problem_3', '/example/url/') + it 'ensure that the XModules have been loaded', -> + expect(XModule.loadModules).toHaveBeenCalled() it 'detect the histrogram element and convert it', -> expect(window.Histogram).toHaveBeenCalledWith('3', [[0, 1]]) From efc572c24d7fc6d4f99a0b3fd39deb05b064e80b Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Jan 2013 14:36:27 -0500 Subject: [PATCH 014/153] Fix navigation jasmine tests --- lms/static/coffee/spec/navigation_spec.coffee | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lms/static/coffee/spec/navigation_spec.coffee b/lms/static/coffee/spec/navigation_spec.coffee index cb98c2b64c..1340984e52 100644 --- a/lms/static/coffee/spec/navigation_spec.coffee +++ b/lms/static/coffee/spec/navigation_spec.coffee @@ -16,6 +16,7 @@ describe 'Navigation', -> active: 1 header: 'h3' autoHeight: false + heightStyle: 'content' describe 'when there is no active section', -> beforeEach -> @@ -23,11 +24,12 @@ describe 'Navigation', -> $('#accordion').append('
') new Navigation - it 'activate the accordian with section 1 as active', -> + it 'activate the accordian with no section as active', -> expect($('#accordion').accordion).toHaveBeenCalledWith - active: 1 + active: 0 header: 'h3' autoHeight: false + heightStyle: 'content' it 'binds the accordionchange event', -> Navigation.bind() From 585f1c41d61aa962220c0d604058077093a77e95 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Jan 2013 16:04:43 -0500 Subject: [PATCH 015/153] New jasmine tests for combined open ended problems. --- .../js/fixtures/combined-open-ended.html | 56 +++++++++++++++++++ .../combinedopenended/display_spec.coffee | 15 +++++ 2 files changed, 71 insertions(+) create mode 100644 common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html create mode 100644 common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee diff --git a/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html b/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html new file mode 100644 index 0000000000..c7170d3472 --- /dev/null +++ b/common/lib/xmodule/xmodule/js/fixtures/combined-open-ended.html @@ -0,0 +1,56 @@ +
+ +

Problem 1

+
+

Status

+
+
+ +
+ + Step 1 (Problem complete) : 1 / 1 + + +
+ +
+ + Step 2 (Being scored) : None / 1 + + +
+
+
+ +
+ +
+

Problem

+
+
+
+ + Some prompt. + +
+
+
+ Submitted for grading. + +
+ +
+ + +
+
+ + + + +
+ + +
+
+
diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee new file mode 100644 index 0000000000..60c73cf1fe --- /dev/null +++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee @@ -0,0 +1,15 @@ +xdescribe 'CombinedOpenEnded', -> + beforeEach -> + spyOn Logger, 'log' + # load up some fixtures + loadFixtures 'combined-open-ended.html' + @element = $('.combined-open-ended') + + describe 'constructor', -> + beforeEach -> + @combined = new CombinedOpenEnded @element + + it 'set the element', -> + except(@combined.element).not.toEqual @element + + #it 'initialize the ajax url, state, and task count' From f1973dc660a5de9bfa24300a51e032fde151ee9c Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Tue, 29 Jan 2013 17:34:54 -0500 Subject: [PATCH 016/153] minor update to new jasmine test --- .../xmodule/js/spec/combinedopenended/display_spec.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee index 60c73cf1fe..3ccf504948 100644 --- a/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee +++ b/common/lib/xmodule/xmodule/js/spec/combinedopenended/display_spec.coffee @@ -12,4 +12,5 @@ xdescribe 'CombinedOpenEnded', -> it 'set the element', -> except(@combined.element).not.toEqual @element - #it 'initialize the ajax url, state, and task count' + #it 'initialize the ajax url, state, and task count', -> + From 7b65f56121f5bc7627890a057d377d6073ecf8d3 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 17 Jan 2013 15:15:58 -0500 Subject: [PATCH 017/153] Add service to query controller for eta of submission and to check if name is unique --- .../controller_query_service.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 lms/djangoapps/open_ended_grading/controller_query_service.py diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py new file mode 100644 index 0000000000..608e37ea09 --- /dev/null +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -0,0 +1,37 @@ +import json +import logging +import requests +from requests.exceptions import RequestException, ConnectionError, HTTPError +import sys +from grading_service import GradingService +from grading_service import GradingServiceError + +from django.conf import settings +from django.http import HttpResponse, Http404 + +log = logging.getLogger(__name__) + +class ControllerQueryService(GradingService): + """ + Interface to staff grading backend. + """ + def __init__(self, config): + super(ControllerQuery, self).__init__(config) + self.check_eta_url = self.url + '/get_submission_eta/' + self.is_unique_url = self.url + '/is_name_unique/' + + def check_if_name_is_unique(self, location, problem_id, course_id): + params = { + 'course_id': course_id, + 'location' : location, + 'problem_id' : problem_id + } + response = self.get(self.is_unique_url, params) + return response + + def check_for_eta(self, location): + params = { + 'location' : location, + } + response = self.get(self.check_eta_url, params) + return response From 43783e4ff07c9a2ff9f9a11b92515d49e66abcb2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 17 Jan 2013 15:18:39 -0500 Subject: [PATCH 018/153] Add in an item to check for combined notifications --- .../open_ended_grading/controller_query_service.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index 608e37ea09..7ab8712c71 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -19,6 +19,7 @@ class ControllerQueryService(GradingService): super(ControllerQuery, self).__init__(config) self.check_eta_url = self.url + '/get_submission_eta/' self.is_unique_url = self.url + '/is_name_unique/' + self.combined_notifications_url = self.url + '/combined_notifications/' def check_if_name_is_unique(self, location, problem_id, course_id): params = { @@ -35,3 +36,12 @@ class ControllerQueryService(GradingService): } response = self.get(self.check_eta_url, params) return response + + def check_combined_notifications(self, course_id, student_id): + params= { + 'student_id' : student_id, + 'course_id' : course_id, + } + response = self.get(self.combined_notifications_url,params) + return response + From c62ce7822e282ac4c34e9deb38d91ca3ecc9ab17 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 17 Jan 2013 16:16:13 -0500 Subject: [PATCH 019/153] Add in service support for getting the grading status list --- .../open_ended_grading/controller_query_service.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index 7ab8712c71..220bc276db 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -20,6 +20,7 @@ class ControllerQueryService(GradingService): self.check_eta_url = self.url + '/get_submission_eta/' self.is_unique_url = self.url + '/is_name_unique/' self.combined_notifications_url = self.url + '/combined_notifications/' + self.grading_status_list_url = self.url + '/get_grading_status_list/' def check_if_name_is_unique(self, location, problem_id, course_id): params = { @@ -37,11 +38,20 @@ class ControllerQueryService(GradingService): response = self.get(self.check_eta_url, params) return response - def check_combined_notifications(self, course_id, student_id): + def check_combined_notifications(self, course_id, student_id, user_is_staff, last_time_viewed): params= { 'student_id' : student_id, 'course_id' : course_id, + 'user_is_staff' : user_is_staff, + 'last_time_viewed' : last_time_viewed, } response = self.get(self.combined_notifications_url,params) return response + def get_grading_status_list(self, course_id, student_id): + params = { + 'student_id' : student_id, + 'course_id' : course_id, + } + + response = self.get(self.grading_status_list_url, params) From d28228639ec86b7733ec23057fa78a04eca3c28b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Thu, 17 Jan 2013 16:58:35 -0500 Subject: [PATCH 020/153] Add in controller query stuff --- lms/djangoapps/courseware/tabs.py | 75 ++++++++++++++++------ lms/djangoapps/open_ended_grading/views.py | 40 ++++++++++++ 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 2ece7f0404..fe37dcde39 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -18,20 +18,12 @@ from django.core.urlresolvers import reverse from fs.errors import ResourceNotFoundError -from lxml.html import rewrite_links - -from module_render import get_module from courseware.access import has_access from static_replace import replace_urls -from xmodule.modulestore import Location -from xmodule.modulestore.django import modulestore -from xmodule.modulestore.xml import XMLModuleStore -from xmodule.x_module import XModule - - from open_ended_grading.peer_grading_service import PeerGradingService from open_ended_grading.staff_grading_service import StaffGradingService +from open_ended_grading.controller_query_service import ControllerQueryService from student.models import unique_id_for_user log = logging.getLogger(__name__) @@ -161,6 +153,38 @@ def _peer_grading(tab, user, course, active_page): return tab return [] +def _combined_open_ended_grading(tab, user, course, active_page): + if user.is_authenticated: + link = reverse('peer_grading', args=[course.id]) + peer_grading_url = settings.PEER_GRADING_INTERFACE + split_url = peer_grading_url.split("/") + controller_url = "http://" + split_url[2] + "/grading_controller" + log.debug(controller_url) + peer_gs = ControllerQueryService(controller_url) + student_id = unique_id_for_user(user) + course_id = course.id + user_is_staff = has_access(user, course, 'staff') + last_time_viewed = 1 + pending_grading= False + tab_name = "Peer grading" + img_path= "" + try: + notifications = json.loads(peer_gs.get_notifications(course.id,)) + if notifications['success']: + if notifications['student_needs_to_peer_grade']: + pending_grading=True + except: + #Non catastrophic error, so no real action + log.info("Problem with getting notifications from peer grading service.") + + if pending_grading: + img_path = "/static/images/slider-handle.png" + + tab = [CourseTab(tab_name, link, active_page == "peer_grading", pending_grading, img_path)] + return tab + return [] + + #### Validators @@ -314,16 +338,27 @@ def get_static_tab_by_slug(course, tab_slug): return None -def get_static_tab_contents(request, cache, course, tab): - loc = Location(course.location.tag, course.location.org, course.location.course, 'static_tab', tab['url_slug']) - tab_module = get_module(request.user, request, loc, cache, course.id) +def get_static_tab_contents(course, tab): + """ + Given a course and a static tab config dict, load the tab contents, + returning None if not found. - logging.debug('course_module = {0}'.format(tab_module)) - - html = '' - - if tab_module is not None: - html = tab_module.get_html() - - return html + Looks in tabs/{course_url_name}/{tab_slug}.html first, then tabs/{tab_slug}.html. + """ + slug = tab['url_slug'] + paths = ['tabs/{0}/{1}.html'.format(course.url_name, slug), + 'tabs/{0}.html'.format(slug)] + fs = course.system.resources_fs + for p in paths: + if fs.exists(p): + try: + with fs.open(p) as tabfile: + # TODO: redundant with module_render.py. Want to be helper methods in static_replace or something. + text = tabfile.read().decode('utf-8') + contents = replace_urls(text, course.metadata['data_dir']) + return replace_urls(contents, staticfiles_prefix='/courses/'+course.id, replace_prefix='/course/') + except (ResourceNotFoundError) as err: + log.exception("Couldn't load tab contents from '{0}': {1}".format(p, err)) + return None + return None diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 858c9a4fd5..c92ce21e21 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -114,5 +114,45 @@ def peer_grading_problem(request, course_id): 'ajax_url': ajax_url, # Checked above 'staff_access': False, }) + +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +def student_problem_list(request, course_id, student_id): + ''' + Show a student problem list + ''' + course = get_course_with_access(request.user, course_id, 'load') + + # call problem list service + success = False + error_text = "" + problem_list = [] + try: + problem_list_json = peer_gs.get_problem_list(course_id, unique_id_for_user(request.user)) + problem_list_dict = json.loads(problem_list_json) + success = problem_list_dict['success'] + if 'error' in problem_list_dict: + error_text = problem_list_dict['error'] + + problem_list = problem_list_dict['problem_list'] + + except GradingServiceError: + error_text = "Error occured while contacting the grading service" + success = False + # catch error if if the json loads fails + except ValueError: + error_text = "Could not get problem list" + success = False + + ajax_url = _reverse_with_slash('peer_grading', course_id) + + return render_to_response('peer_grading/peer_grading.html', { + 'course': course, + 'course_id': course_id, + 'ajax_url': ajax_url, + 'success': success, + 'problem_list': problem_list, + 'error_text': error_text, + # Checked above + 'staff_access': False, }) From 5e63a4690be87a50ed5c6678faa578b18c4396de Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:18:04 -0500 Subject: [PATCH 021/153] Work on getting combined notifications --- lms/djangoapps/courseware/tabs.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index fe37dcde39..90c7687142 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -25,6 +25,8 @@ from open_ended_grading.peer_grading_service import PeerGradingService from open_ended_grading.staff_grading_service import StaffGradingService from open_ended_grading.controller_query_service import ControllerQueryService from student.models import unique_id_for_user +from models import StudentModule +import datetime log = logging.getLogger(__name__) @@ -160,27 +162,31 @@ def _combined_open_ended_grading(tab, user, course, active_page): split_url = peer_grading_url.split("/") controller_url = "http://" + split_url[2] + "/grading_controller" log.debug(controller_url) - peer_gs = ControllerQueryService(controller_url) + controller_qs = ControllerQueryService(controller_url) student_id = unique_id_for_user(user) course_id = course.id user_is_staff = has_access(user, course, 'staff') - last_time_viewed = 1 + + min_time_to_query = user.last_login + last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified>min_time_to_query).values('modified').order_by('-modified')[0] + + last_time_viewed = last_module_seen['modified'] pending_grading= False - tab_name = "Peer grading" + tab_name = "Open Ended Questions" img_path= "" try: - notifications = json.loads(peer_gs.get_notifications(course.id,)) + notifications = json.loads(controller_qs.get_notifications(course.id,student_id, user_is_staff, last_time_viewed)) if notifications['success']: if notifications['student_needs_to_peer_grade']: pending_grading=True except: #Non catastrophic error, so no real action - log.info("Problem with getting notifications from peer grading service.") + log.info("Problem with getting notifications from controller query service.") if pending_grading: img_path = "/static/images/slider-handle.png" - tab = [CourseTab(tab_name, link, active_page == "peer_grading", pending_grading, img_path)] + tab = [CourseTab(tab_name, link, active_page == "controller_query", pending_grading, img_path)] return tab return [] From fe77bb00c411c88597ba5c9ea13e94b557487447 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:19:34 -0500 Subject: [PATCH 022/153] Fix naming for overall need to check --- lms/djangoapps/courseware/tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 90c7687142..1850ae3faf 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -177,7 +177,7 @@ def _combined_open_ended_grading(tab, user, course, active_page): try: notifications = json.loads(controller_qs.get_notifications(course.id,student_id, user_is_staff, last_time_viewed)) if notifications['success']: - if notifications['student_needs_to_peer_grade']: + if notifications['overall_need_to_check']: pending_grading=True except: #Non catastrophic error, so no real action From 2191f3e228548119ca624ffb13870c0a3d14123a Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:30:03 -0500 Subject: [PATCH 023/153] Add in open ended problems view --- lms/djangoapps/courseware/tabs.py | 2 +- lms/djangoapps/open_ended_grading/views.py | 9 +++-- .../open_ended_problems.html | 36 +++++++++++++++++++ lms/urls.py | 6 +++- 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 lms/templates/open_ended_problems/open_ended_problems.html diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 1850ae3faf..80c353c748 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -157,7 +157,7 @@ def _peer_grading(tab, user, course, active_page): def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated: - link = reverse('peer_grading', args=[course.id]) + link = reverse('open_ended_problems', args=[course.id]) peer_grading_url = settings.PEER_GRADING_INTERFACE split_url = peer_grading_url.split("/") controller_url = "http://" + split_url[2] + "/grading_controller" diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index c92ce21e21..471e00f5f0 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -13,6 +13,7 @@ from courseware.courses import get_course_with_access from peer_grading_service import PeerGradingService from peer_grading_service import MockPeerGradingService +from controller_query_service import ControllerQueryService from grading_service import GradingServiceError import json from .staff_grading import StaffGrading @@ -26,6 +27,8 @@ if settings.MOCK_PEER_GRADING: else: peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) +controller_qs = ControllerQueryService(settings.PEER_GRADING_INTERFACE) + """ Reverses the URL from the name and the course id, and then adds a trailing slash if it does not exist yet @@ -127,7 +130,7 @@ def student_problem_list(request, course_id, student_id): error_text = "" problem_list = [] try: - problem_list_json = peer_gs.get_problem_list(course_id, unique_id_for_user(request.user)) + problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user)) problem_list_dict = json.loads(problem_list_json) success = problem_list_dict['success'] if 'error' in problem_list_dict: @@ -143,9 +146,9 @@ def student_problem_list(request, course_id, student_id): error_text = "Could not get problem list" success = False - ajax_url = _reverse_with_slash('peer_grading', course_id) + ajax_url = _reverse_with_slash('open_ended_problems', course_id) - return render_to_response('peer_grading/peer_grading.html', { + return render_to_response('open_ended_problems/open_ended_problems.html', { 'course': course, 'course_id': course_id, 'ajax_url': ajax_url, diff --git a/lms/templates/open_ended_problems/open_ended_problems.html b/lms/templates/open_ended_problems/open_ended_problems.html new file mode 100644 index 0000000000..ec648afab8 --- /dev/null +++ b/lms/templates/open_ended_problems/open_ended_problems.html @@ -0,0 +1,36 @@ +<%inherit file="/main.html" /> +<%block name="bodyclass">${course.css_class} +<%namespace name='static' file='/static_content.html'/> + +<%block name="headextra"> +<%static:css group='course'/> + + +<%block name="title">${course.number} Open Ended Problems + +<%include file="/courseware/course_navigation.html" args="active_page='open_ended_problems'" /> + + +
+
+
${error_text}
+

Open Ended Problems

+

Instructions

+

Here are a list of open ended problems for this course.

+ % if success: + % if len(problem_list) == 0: +
+ You have not attempted any open ended problems yet. +
+ %else: + + %endif + %endif +
+
\ No newline at end of file diff --git a/lms/urls.py b/lms/urls.py index f92b63aac2..c7c4f231f9 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -282,7 +282,11 @@ if settings.COURSEWARE_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/peer_grading/save_calibration_essay$', 'open_ended_grading.peer_grading_service.save_calibration_essay', name='peer_grading_save_calibration_essay'), - # Cohorts management + # Open Ended problem list + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_problems$', + 'open_ended_grading.views.student_problem_list', name='open_ended_problems'), + + # Cohorts management url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts$', 'course_groups.views.list_cohorts', name="cohorts"), url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts/add$', From b0599ea133d00bdff3b54975258d786620dd746f Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:34:48 -0500 Subject: [PATCH 024/153] Fix queries and some controller query stuff --- lms/djangoapps/courseware/tabs.py | 2 +- lms/djangoapps/open_ended_grading/controller_query_service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 80c353c748..75bb61b59a 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -168,7 +168,7 @@ def _combined_open_ended_grading(tab, user, course, active_page): user_is_staff = has_access(user, course, 'staff') min_time_to_query = user.last_login - last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified>min_time_to_query).values('modified').order_by('-modified')[0] + last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] last_time_viewed = last_module_seen['modified'] pending_grading= False diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index 220bc276db..649f65dfac 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -16,7 +16,7 @@ class ControllerQueryService(GradingService): Interface to staff grading backend. """ def __init__(self, config): - super(ControllerQuery, self).__init__(config) + super(ControllerQueryService, self).__init__(config) self.check_eta_url = self.url + '/get_submission_eta/' self.is_unique_url = self.url + '/is_name_unique/' self.combined_notifications_url = self.url + '/combined_notifications/' From e6dd07a41e3b2b31c9317ab5b22445db605781f2 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:39:49 -0500 Subject: [PATCH 025/153] Fix parameter passing --- lms/djangoapps/open_ended_grading/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 471e00f5f0..27be3d0939 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -17,6 +17,7 @@ from controller_query_service import ControllerQueryService from grading_service import GradingServiceError import json from .staff_grading import StaffGrading +from student.models import unique_id_for_user log = logging.getLogger(__name__) @@ -119,11 +120,12 @@ def peer_grading_problem(request, course_id): 'staff_access': False, }) @cache_control(no_cache=True, no_store=True, must_revalidate=True) -def student_problem_list(request, course_id, student_id): +def student_problem_list(request, course_id): ''' Show a student problem list ''' course = get_course_with_access(request.user, course_id, 'load') + student_id = unique_id_for_user(request.user) # call problem list service success = False From 693337593e928c2bb8d52d42b7691908b4f4b9e5 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:43:08 -0500 Subject: [PATCH 026/153] Properly parse url for grading controller --- lms/djangoapps/courseware/tabs.py | 7 +++---- lms/djangoapps/open_ended_grading/open_ended_util.py | 7 +++++++ lms/djangoapps/open_ended_grading/views.py | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 lms/djangoapps/open_ended_grading/open_ended_util.py diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 75bb61b59a..01742092aa 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -24,6 +24,7 @@ from static_replace import replace_urls from open_ended_grading.peer_grading_service import PeerGradingService from open_ended_grading.staff_grading_service import StaffGradingService from open_ended_grading.controller_query_service import ControllerQueryService +from open_ended_grading import open_ended_util from student.models import unique_id_for_user from models import StudentModule import datetime @@ -158,10 +159,8 @@ def _peer_grading(tab, user, course, active_page): def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated: link = reverse('open_ended_problems', args=[course.id]) - peer_grading_url = settings.PEER_GRADING_INTERFACE - split_url = peer_grading_url.split("/") - controller_url = "http://" + split_url[2] + "/grading_controller" - log.debug(controller_url) + + controller_url = open_ended_util.get_controller_url() controller_qs = ControllerQueryService(controller_url) student_id = unique_id_for_user(user) course_id = course.id diff --git a/lms/djangoapps/open_ended_grading/open_ended_util.py b/lms/djangoapps/open_ended_grading/open_ended_util.py new file mode 100644 index 0000000000..b50cf3afb2 --- /dev/null +++ b/lms/djangoapps/open_ended_grading/open_ended_util.py @@ -0,0 +1,7 @@ +from django.conf import settings + +def get_controller_url(): + peer_grading_url = settings.PEER_GRADING_INTERFACE + split_url = peer_grading_url.split("/") + controller_url = "http://" + split_url[2] + "/grading_controller" + return controller_url diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 27be3d0939..3928fb7b85 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -19,6 +19,8 @@ import json from .staff_grading import StaffGrading from student.models import unique_id_for_user +import open_ended_util + log = logging.getLogger(__name__) @@ -28,7 +30,8 @@ if settings.MOCK_PEER_GRADING: else: peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) -controller_qs = ControllerQueryService(settings.PEER_GRADING_INTERFACE) +controller_url = open_ended_util.get_controller_url() +controller_qs = ControllerQueryService(controller_url) """ Reverses the URL from the name and the course id, and then adds a trailing slash if From f4574c75db61af0db9a99d571b7569ad11a60d4e Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:54:14 -0500 Subject: [PATCH 027/153] Proper config dictionary generation for controller query --- lms/djangoapps/open_ended_grading/open_ended_util.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/open_ended_util.py b/lms/djangoapps/open_ended_grading/open_ended_util.py index b50cf3afb2..772e3cd08e 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_util.py +++ b/lms/djangoapps/open_ended_grading/open_ended_util.py @@ -1,7 +1,12 @@ from django.conf import settings +import logging + +log=logging.getLogger(__name__) def get_controller_url(): - peer_grading_url = settings.PEER_GRADING_INTERFACE + peer_grading_url = settings.PEER_GRADING_INTERFACE['url'] split_url = peer_grading_url.split("/") controller_url = "http://" + split_url[2] + "/grading_controller" - return controller_url + controller_settings=settings.PEER_GRADING_INTERFACE + controller_settings['url'] = controller_url + return controller_settings From 61d62f274e526fe6a366312dc2b90285fd4a4c8b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:57:20 -0500 Subject: [PATCH 028/153] Fix issue with parsing of open ended list --- lms/djangoapps/open_ended_grading/controller_query_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index 649f65dfac..a111cfe58d 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -55,3 +55,4 @@ class ControllerQueryService(GradingService): } response = self.get(self.grading_status_list_url, params) + return response From 68db8b0d77f906994108a5c876260e26867b74e7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 11:46:46 -0500 Subject: [PATCH 029/153] Refactor tab logic, start adding in combined notifications view --- lms/djangoapps/courseware/tabs.py | 64 ++--------------- .../open_ended_notifications.py | 72 +++++++++++++++++++ lms/djangoapps/open_ended_grading/views.py | 4 +- .../combined_notifications.html | 41 +++++++++++ 4 files changed, 121 insertions(+), 60 deletions(-) create mode 100644 lms/djangoapps/open_ended_grading/open_ended_notifications.py create mode 100644 lms/templates/open_ended_problems/combined_notifications.html diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 01742092aa..326c1a5cbd 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -21,13 +21,7 @@ from fs.errors import ResourceNotFoundError from courseware.access import has_access from static_replace import replace_urls -from open_ended_grading.peer_grading_service import PeerGradingService -from open_ended_grading.staff_grading_service import StaffGradingService -from open_ended_grading.controller_query_service import ControllerQueryService -from open_ended_grading import open_ended_util -from student.models import unique_id_for_user -from models import StudentModule -import datetime +from open_ended_grading import open_ended_notifications log = logging.getLogger(__name__) @@ -113,21 +107,8 @@ def _textbooks(tab, user, course, active_page): def _staff_grading(tab, user, course, active_page): if has_access(user, course, 'staff'): link = reverse('staff_grading', args=[course.id]) - staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) - pending_grading=False tab_name = "Staff grading" - img_path= "" - try: - notifications = json.loads(staff_gs.get_notifications(course.id)) - if notifications['success']: - if notifications['staff_needs_to_grade']: - pending_grading=True - except: - #Non catastrophic error, so no real action - log.info("Problem with getting notifications from staff grading service.") - - if pending_grading: - img_path = "/static/images/slider-handle.png" + pending_grading, img_path = open_ended_notifications.staff_grading_notifications(course) tab = [CourseTab(tab_name, link, active_page == "staff_grading", pending_grading, img_path)] return tab @@ -136,21 +117,8 @@ def _staff_grading(tab, user, course, active_page): def _peer_grading(tab, user, course, active_page): if user.is_authenticated(): link = reverse('peer_grading', args=[course.id]) - peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) - pending_grading=False tab_name = "Peer grading" - img_path= "" - try: - notifications = json.loads(peer_gs.get_notifications(course.id,unique_id_for_user(user))) - if notifications['success']: - if notifications['student_needs_to_peer_grade']: - pending_grading=True - except: - #Non catastrophic error, so no real action - log.info("Problem with getting notifications from peer grading service.") - - if pending_grading: - img_path = "/static/images/slider-handle.png" + pending_grading, img_path = open_ended_notifications.peer_grading_notifications(course, user) tab = [CourseTab(tab_name, link, active_page == "peer_grading", pending_grading, img_path)] return tab @@ -159,31 +127,9 @@ def _peer_grading(tab, user, course, active_page): def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated: link = reverse('open_ended_problems', args=[course.id]) - - controller_url = open_ended_util.get_controller_url() - controller_qs = ControllerQueryService(controller_url) - student_id = unique_id_for_user(user) - course_id = course.id - user_is_staff = has_access(user, course, 'staff') - - min_time_to_query = user.last_login - last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] - - last_time_viewed = last_module_seen['modified'] - pending_grading= False tab_name = "Open Ended Questions" - img_path= "" - try: - notifications = json.loads(controller_qs.get_notifications(course.id,student_id, user_is_staff, last_time_viewed)) - if notifications['success']: - if notifications['overall_need_to_check']: - pending_grading=True - except: - #Non catastrophic error, so no real action - log.info("Problem with getting notifications from controller query service.") - - if pending_grading: - img_path = "/static/images/slider-handle.png" + + pending_grading, img_path = open_ended_notifications.combined_notifications(course, user) tab = [CourseTab(tab_name, link, active_page == "controller_query", pending_grading, img_path)] return tab diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py new file mode 100644 index 0000000000..68f1509891 --- /dev/null +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -0,0 +1,72 @@ +from django.conf import settings +from staff_grading_service import StaffGradingService +from peer_grading_service import PeerGradingService +from open_ended_grading.controller_query_service import ControllerQueryService +import json +from student.models import unique_id_for_user +import open_ended_util +from courseware.models import StudentModule + +def staff_grading_notifications(course): + staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) + pending_grading=False + img_path= "" + try: + notifications = json.loads(staff_gs.get_notifications(course.id)) + if notifications['success']: + if notifications['staff_needs_to_grade']: + pending_grading=True + except: + #Non catastrophic error, so no real action + log.info("Problem with getting notifications from staff grading service.") + + if pending_grading: + img_path = "/static/images/slider-handle.png" + + return pending_grading, img_path + +def peer_grading_notifications(course, user): + peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) + pending_grading=False + img_path= "" + try: + notifications = json.loads(peer_gs.get_notifications(course.id,unique_id_for_user(user))) + if notifications['success']: + if notifications['student_needs_to_peer_grade']: + pending_grading=True + except: + #Non catastrophic error, so no real action + log.info("Problem with getting notifications from peer grading service.") + + if pending_grading: + img_path = "/static/images/slider-handle.png" + + return pending_grading, img_path + +def combined_notifications(course, user): + controller_url = open_ended_util.get_controller_url() + controller_qs = ControllerQueryService(controller_url) + student_id = unique_id_for_user(user) + course_id = course.id + user_is_staff = has_access(user, course, 'staff') + + min_time_to_query = user.last_login + last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] + + last_time_viewed = last_module_seen['modified'] + pending_grading= False + + img_path= "" + try: + notifications = json.loads(controller_qs.get_notifications(course.id,student_id, user_is_staff, last_time_viewed)) + if notifications['success']: + if notifications['overall_need_to_check']: + pending_grading=True + except: + #Non catastrophic error, so no real action + log.info("Problem with getting notifications from controller query service.") + + if pending_grading: + img_path = "/static/images/slider-handle.png" + + return pending_grading, img_path \ No newline at end of file diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 3928fb7b85..3da1fb0835 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -21,7 +21,6 @@ from student.models import unique_id_for_user import open_ended_util - log = logging.getLogger(__name__) template_imports = {'urllib': urllib} @@ -162,5 +161,8 @@ def student_problem_list(request, course_id): 'error_text': error_text, # Checked above 'staff_access': False, }) + +def combined_notifications(request, course_id): + pass diff --git a/lms/templates/open_ended_problems/combined_notifications.html b/lms/templates/open_ended_problems/combined_notifications.html new file mode 100644 index 0000000000..6cd2a095dc --- /dev/null +++ b/lms/templates/open_ended_problems/combined_notifications.html @@ -0,0 +1,41 @@ +<%inherit file="/main.html" /> +<%block name="bodyclass">${course.css_class} +<%namespace name='static' file='/static_content.html'/> + +<%block name="headextra"> +<%static:css group='course'/> + + +<%block name="title">${course.number} Combined Notifications + +<%include file="/courseware/course_navigation.html" args="active_page='combined_notifications'" /> + + +
+
+
${error_text}
+

Open Ended Console

+

Instructions

+

Here are items that could potentially need your attention.

+ % if success: + % if len(item_list) == 0: +
+ No items require attention at the moment. +
+ %else: + + %endif + %endif +
+
\ No newline at end of file From 4112f8ad8e0949823863f3af1e016536fe0fbc66 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 11:53:46 -0500 Subject: [PATCH 030/153] Pass back dictionaries where needed from notifications functions --- lms/djangoapps/courseware/tabs.py | 16 ++++++++++++---- .../open_ended_notifications.py | 6 +++--- lms/djangoapps/open_ended_grading/views.py | 4 +++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 326c1a5cbd..dfe5cb14b9 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -108,7 +108,10 @@ def _staff_grading(tab, user, course, active_page): if has_access(user, course, 'staff'): link = reverse('staff_grading', args=[course.id]) tab_name = "Staff grading" - pending_grading, img_path = open_ended_notifications.staff_grading_notifications(course) + + notifications = open_ended_notifications.staff_grading_notifications(course) + pending_grading = notifications['pending_grading'] + img_path = notifications['img_path'] tab = [CourseTab(tab_name, link, active_page == "staff_grading", pending_grading, img_path)] return tab @@ -118,7 +121,10 @@ def _peer_grading(tab, user, course, active_page): if user.is_authenticated(): link = reverse('peer_grading', args=[course.id]) tab_name = "Peer grading" - pending_grading, img_path = open_ended_notifications.peer_grading_notifications(course, user) + + notifications = open_ended_notifications.peer_grading_notifications(course, user) + pending_grading = notifications['pending_grading'] + img_path = notifications['img_path'] tab = [CourseTab(tab_name, link, active_page == "peer_grading", pending_grading, img_path)] return tab @@ -128,8 +134,10 @@ def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated: link = reverse('open_ended_problems', args=[course.id]) tab_name = "Open Ended Questions" - - pending_grading, img_path = open_ended_notifications.combined_notifications(course, user) + + notifications = open_ended_notifications.combined_notifications(course, user) + pending_grading = notifications['pending_grading'] + img_path = notifications['img_path'] tab = [CourseTab(tab_name, link, active_page == "controller_query", pending_grading, img_path)] return tab diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index 68f1509891..d394769f1e 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -23,7 +23,7 @@ def staff_grading_notifications(course): if pending_grading: img_path = "/static/images/slider-handle.png" - return pending_grading, img_path + return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} def peer_grading_notifications(course, user): peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) @@ -41,7 +41,7 @@ def peer_grading_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - return pending_grading, img_path + return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} def combined_notifications(course, user): controller_url = open_ended_util.get_controller_url() @@ -69,4 +69,4 @@ def combined_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - return pending_grading, img_path \ No newline at end of file + return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} \ No newline at end of file diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 3da1fb0835..e6841ccbd3 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -20,6 +20,7 @@ from .staff_grading import StaffGrading from student.models import unique_id_for_user import open_ended_util +import open_ended_notifications log = logging.getLogger(__name__) @@ -163,6 +164,7 @@ def student_problem_list(request, course_id): 'staff_access': False, }) def combined_notifications(request, course_id): - pass + user = request.user + From 22a2bdaa3d735b175d312cf26e85f066239775dc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 11:57:26 -0500 Subject: [PATCH 031/153] Disable student problem list caching, build out notifications --- lms/djangoapps/courseware/tabs.py | 8 ++++---- .../open_ended_grading/open_ended_notifications.py | 11 +++++------ lms/djangoapps/open_ended_grading/views.py | 3 +++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index dfe5cb14b9..7a8367a42b 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -108,8 +108,8 @@ def _staff_grading(tab, user, course, active_page): if has_access(user, course, 'staff'): link = reverse('staff_grading', args=[course.id]) tab_name = "Staff grading" - - notifications = open_ended_notifications.staff_grading_notifications(course) + + notifications = open_ended_notifications.staff_grading_notifications(course_id) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] @@ -122,7 +122,7 @@ def _peer_grading(tab, user, course, active_page): link = reverse('peer_grading', args=[course.id]) tab_name = "Peer grading" - notifications = open_ended_notifications.peer_grading_notifications(course, user) + notifications = open_ended_notifications.peer_grading_notifications(course_id, user) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] @@ -135,7 +135,7 @@ def _combined_open_ended_grading(tab, user, course, active_page): link = reverse('open_ended_problems', args=[course.id]) tab_name = "Open Ended Questions" - notifications = open_ended_notifications.combined_notifications(course, user) + notifications = open_ended_notifications.combined_notifications(course_id, user) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index d394769f1e..31b7a40dff 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -7,12 +7,12 @@ from student.models import unique_id_for_user import open_ended_util from courseware.models import StudentModule -def staff_grading_notifications(course): +def staff_grading_notifications(course_id): staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) pending_grading=False img_path= "" try: - notifications = json.loads(staff_gs.get_notifications(course.id)) + notifications = json.loads(staff_gs.get_notifications(course_id)) if notifications['success']: if notifications['staff_needs_to_grade']: pending_grading=True @@ -25,12 +25,12 @@ def staff_grading_notifications(course): return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} -def peer_grading_notifications(course, user): +def peer_grading_notifications(course_id, user): peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) pending_grading=False img_path= "" try: - notifications = json.loads(peer_gs.get_notifications(course.id,unique_id_for_user(user))) + notifications = json.loads(peer_gs.get_notifications(course_id,unique_id_for_user(user))) if notifications['success']: if notifications['student_needs_to_peer_grade']: pending_grading=True @@ -43,11 +43,10 @@ def peer_grading_notifications(course, user): return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} -def combined_notifications(course, user): +def combined_notifications(course_id, user): controller_url = open_ended_util.get_controller_url() controller_qs = ControllerQueryService(controller_url) student_id = unique_id_for_user(user) - course_id = course.id user_is_staff = has_access(user, course, 'staff') min_time_to_query = user.last_login diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index e6841ccbd3..3797c2fbb2 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -163,8 +163,11 @@ def student_problem_list(request, course_id): # Checked above 'staff_access': False, }) +@cache_control(no_cache=True, no_store=True, must_revalidate=True) def combined_notifications(request, course_id): user = request.user + notifications = open_ended_notifications.combined_notifications(course_id, user) + response = notifications['response'] From 6b7ba3799993c2a8a48d78f6b39fccc4e303ae5b Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 12:09:34 -0500 Subject: [PATCH 032/153] Combined notification view --- .../open_ended_notifications.py | 6 +++++ lms/djangoapps/open_ended_grading/views.py | 25 +++++++++++++++++++ .../combined_notifications.html | 6 ++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index 31b7a40dff..516b19e326 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -7,6 +7,12 @@ from student.models import unique_id_for_user import open_ended_util from courseware.models import StudentModule +NOTIFICATION_TYPES = ( + ('student_needs_to_peer_grade', 'peer_grading', 'Peer Grading'), + ('staff_needs_to_grade', 'staff_grading', 'Staff Grading'), + ('overall_need_to_check', 'open_ended_problems', 'Problems you have submitted') + ) + def staff_grading_notifications(course_id): staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) pending_grading=False diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 3797c2fbb2..a1907ae230 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -168,6 +168,31 @@ def combined_notifications(request, course_id): user = request.user notifications = open_ended_notifications.combined_notifications(course_id, user) response = notifications['response'] + notificaton_tuples=open_ended_notifications.NOTIFICATION_TYPES + notification_list = [] + for response_num in xrange(0,len(notification_tuples)): + tag=notification_tuples[response_num][0] + if tag in response: + url_name = notification_tuples[response_num][1] + human_name = notification_tuples[response_num][2] + url = _reverse_with_slash(url_name, course_id) + has_img = response[tag] + img_path = "/static/images/slider-handle.png" + + notification_item = { + 'url' : url, + 'name' : human_name, + 'has_img' : has_img, + 'img' : img_path, + } + notification_list.append(notification_item) + + combined_dict = { + 'error_text' : "", + 'notification_list' : notification_list, + } + + return combined_dict diff --git a/lms/templates/open_ended_problems/combined_notifications.html b/lms/templates/open_ended_problems/combined_notifications.html index 6cd2a095dc..9fdedb2555 100644 --- a/lms/templates/open_ended_problems/combined_notifications.html +++ b/lms/templates/open_ended_problems/combined_notifications.html @@ -18,7 +18,7 @@

Instructions

Here are items that could potentially need your attention.

% if success: - % if len(item_list) == 0: + % if len(notification_list) == 0:
No items require attention at the moment.
@@ -28,8 +28,8 @@
  • {notification['name']} - % if notification.has_img == True: - + % if notification['has_img'] == True: + %endif
  • From 0f212cc8f291847efa799c329dcc66dddac93b83 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 12:53:20 -0500 Subject: [PATCH 033/153] Fix course id passing, template rendering --- lms/djangoapps/courseware/tabs.py | 6 +++--- .../open_ended_notifications.py | 21 ++++++++++++++----- lms/djangoapps/open_ended_grading/views.py | 11 +++++++--- lms/urls.py | 5 ++++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 7a8367a42b..3aa502e95e 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -109,7 +109,7 @@ def _staff_grading(tab, user, course, active_page): link = reverse('staff_grading', args=[course.id]) tab_name = "Staff grading" - notifications = open_ended_notifications.staff_grading_notifications(course_id) + notifications = open_ended_notifications.staff_grading_notifications(course) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] @@ -122,7 +122,7 @@ def _peer_grading(tab, user, course, active_page): link = reverse('peer_grading', args=[course.id]) tab_name = "Peer grading" - notifications = open_ended_notifications.peer_grading_notifications(course_id, user) + notifications = open_ended_notifications.peer_grading_notifications(course, user) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] @@ -135,7 +135,7 @@ def _combined_open_ended_grading(tab, user, course, active_page): link = reverse('open_ended_problems', args=[course.id]) tab_name = "Open Ended Questions" - notifications = open_ended_notifications.combined_notifications(course_id, user) + notifications = open_ended_notifications.combined_notifications(course, user) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index 516b19e326..9ab75833ca 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -6,6 +6,10 @@ import json from student.models import unique_id_for_user import open_ended_util from courseware.models import StudentModule +import logging +from courseware.access import has_access + +log=logging.getLogger(__name__) NOTIFICATION_TYPES = ( ('student_needs_to_peer_grade', 'peer_grading', 'Peer Grading'), @@ -13,10 +17,11 @@ NOTIFICATION_TYPES = ( ('overall_need_to_check', 'open_ended_problems', 'Problems you have submitted') ) -def staff_grading_notifications(course_id): +def staff_grading_notifications(course): staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) pending_grading=False img_path= "" + course_id = course.id try: notifications = json.loads(staff_gs.get_notifications(course_id)) if notifications['success']: @@ -24,6 +29,7 @@ def staff_grading_notifications(course_id): pending_grading=True except: #Non catastrophic error, so no real action + notifications = {} log.info("Problem with getting notifications from staff grading service.") if pending_grading: @@ -31,10 +37,12 @@ def staff_grading_notifications(course_id): return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} -def peer_grading_notifications(course_id, user): +def peer_grading_notifications(course, user): peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) pending_grading=False img_path= "" + course_id = course.id + try: notifications = json.loads(peer_gs.get_notifications(course_id,unique_id_for_user(user))) if notifications['success']: @@ -42,6 +50,7 @@ def peer_grading_notifications(course_id, user): pending_grading=True except: #Non catastrophic error, so no real action + notifications = {} log.info("Problem with getting notifications from peer grading service.") if pending_grading: @@ -49,14 +58,15 @@ def peer_grading_notifications(course_id, user): return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} -def combined_notifications(course_id, user): +def combined_notifications(course, user): controller_url = open_ended_util.get_controller_url() controller_qs = ControllerQueryService(controller_url) student_id = unique_id_for_user(user) - user_is_staff = has_access(user, course, 'staff') + user_is_staff = has_access(user, course, 'staff') + course_id = course.id min_time_to_query = user.last_login - last_module_seen = StudentModule.objects.all(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] + last_module_seen = StudentModule.objects.filter(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] last_time_viewed = last_module_seen['modified'] pending_grading= False @@ -69,6 +79,7 @@ def combined_notifications(course_id, user): pending_grading=True except: #Non catastrophic error, so no real action + notifications = {} log.info("Problem with getting notifications from controller query service.") if pending_grading: diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index a1907ae230..c617eaac3f 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -165,10 +165,12 @@ def student_problem_list(request, course_id): @cache_control(no_cache=True, no_store=True, must_revalidate=True) def combined_notifications(request, course_id): + course = get_course_with_access(request.user, course_id, 'load') user = request.user - notifications = open_ended_notifications.combined_notifications(course_id, user) + notifications = open_ended_notifications.combined_notifications(course, user) response = notifications['response'] - notificaton_tuples=open_ended_notifications.NOTIFICATION_TYPES + notification_tuples=open_ended_notifications.NOTIFICATION_TYPES + notification_list = [] for response_num in xrange(0,len(notification_tuples)): @@ -191,8 +193,11 @@ def combined_notifications(request, course_id): combined_dict = { 'error_text' : "", 'notification_list' : notification_list, + 'course' : course, } - return combined_dict + return render_to_response('open_ended_problems/combined_notifications.html', + combined_dict + ) diff --git a/lms/urls.py b/lms/urls.py index c7c4f231f9..4b3cc94cab 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -285,7 +285,7 @@ if settings.COURSEWARE_ENABLED: # Open Ended problem list url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_problems$', 'open_ended_grading.views.student_problem_list', name='open_ended_problems'), - + # Cohorts management url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/cohorts$', 'course_groups.views.list_cohorts', name="cohorts"), @@ -305,6 +305,9 @@ if settings.COURSEWARE_ENABLED: 'course_groups.views.debug_cohort_mgmt', name="debug_cohort_mgmt"), + # Open Ended Notifications + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/open_ended_notifications$', + 'open_ended_grading.views.combined_notifications', name='open_ended_notifications'), ) # discussion forums live within courseware, so courseware must be enabled first From 8602e1595bf7819dd7ff1aad13f4c350d18e63a4 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 13:13:57 -0500 Subject: [PATCH 034/153] Pass proper parameters, fix templating --- .../open_ended_notifications.py | 6 ++-- .../open_ended_grading/open_ended_util.py | 2 +- lms/djangoapps/open_ended_grading/views.py | 7 +++- .../combined_notifications.html | 36 +++++++++---------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index 9ab75833ca..a5faef1011 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -73,14 +73,16 @@ def combined_notifications(course, user): img_path= "" try: - notifications = json.loads(controller_qs.get_notifications(course.id,student_id, user_is_staff, last_time_viewed)) + controller_response = controller_qs.check_combined_notifications(course.id,student_id, user_is_staff, last_time_viewed) + log.debug(controller_response) + notifications = json.loads(controller_response) if notifications['success']: if notifications['overall_need_to_check']: pending_grading=True except: #Non catastrophic error, so no real action notifications = {} - log.info("Problem with getting notifications from controller query service.") + log.exception("Problem with getting notifications from controller query service.") if pending_grading: img_path = "/static/images/slider-handle.png" diff --git a/lms/djangoapps/open_ended_grading/open_ended_util.py b/lms/djangoapps/open_ended_grading/open_ended_util.py index 772e3cd08e..07744d7d2c 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_util.py +++ b/lms/djangoapps/open_ended_grading/open_ended_util.py @@ -7,6 +7,6 @@ def get_controller_url(): peer_grading_url = settings.PEER_GRADING_INTERFACE['url'] split_url = peer_grading_url.split("/") controller_url = "http://" + split_url[2] + "/grading_controller" - controller_settings=settings.PEER_GRADING_INTERFACE + controller_settings=settings.PEER_GRADING_INTERFACE.copy() controller_settings['url'] = controller_url return controller_settings diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index c617eaac3f..bfa7d89337 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -168,10 +168,10 @@ def combined_notifications(request, course_id): course = get_course_with_access(request.user, course_id, 'load') user = request.user notifications = open_ended_notifications.combined_notifications(course, user) + log.debug(notifications) response = notifications['response'] notification_tuples=open_ended_notifications.NOTIFICATION_TYPES - notification_list = [] for response_num in xrange(0,len(notification_tuples)): tag=notification_tuples[response_num][0] @@ -190,12 +190,17 @@ def combined_notifications(request, course_id): } notification_list.append(notification_item) + ajax_url = _reverse_with_slash('open_ended_notifications', course_id) combined_dict = { 'error_text' : "", 'notification_list' : notification_list, 'course' : course, + 'success' : True, + 'ajax_url' : ajax_url, } + log.debug(combined_dict) + return render_to_response('open_ended_problems/combined_notifications.html', combined_dict ) diff --git a/lms/templates/open_ended_problems/combined_notifications.html b/lms/templates/open_ended_problems/combined_notifications.html index 9fdedb2555..d14b2b9d9c 100644 --- a/lms/templates/open_ended_problems/combined_notifications.html +++ b/lms/templates/open_ended_problems/combined_notifications.html @@ -18,24 +18,24 @@

    Instructions

    Here are items that could potentially need your attention.

    % if success: - % if len(notification_list) == 0: -
    - No items require attention at the moment. -
    - %else: - - %endif + % if len(notification_list) == 0: +
    + No items require attention at the moment. +
    + %else: + + %endif %endif
    \ No newline at end of file From f47c431fafb4bba475ab0b093910aa07b21174c6 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 13:22:30 -0500 Subject: [PATCH 035/153] Fix templating --- .../open_ended_grading/controller_query_service.py | 1 + lms/djangoapps/open_ended_grading/views.py | 7 +++++-- .../open_ended_problems/combined_notifications.html | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/controller_query_service.py b/lms/djangoapps/open_ended_grading/controller_query_service.py index a111cfe58d..7d515e2475 100644 --- a/lms/djangoapps/open_ended_grading/controller_query_service.py +++ b/lms/djangoapps/open_ended_grading/controller_query_service.py @@ -45,6 +45,7 @@ class ControllerQueryService(GradingService): 'user_is_staff' : user_is_staff, 'last_time_viewed' : last_time_viewed, } + log.debug(self.combined_notifications_url) response = self.get(self.combined_notifications_url,params) return response diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index bfa7d89337..84bf14bf0e 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -39,11 +39,14 @@ it does not exist yet """ def _reverse_with_slash(url_name, course_id): - ajax_url = reverse(url_name, kwargs={'course_id': course_id}) + ajax_url = _reverse_without_slash(url_name, course_id) if not ajax_url.endswith('/'): ajax_url += '/' return ajax_url +def _reverse_without_slash(url_name, course_id): + ajax_url = reverse(url_name, kwargs={'course_id': course_id}) + return ajax_url @cache_control(no_cache=True, no_store=True, must_revalidate=True) def staff_grading(request, course_id): @@ -178,7 +181,7 @@ def combined_notifications(request, course_id): if tag in response: url_name = notification_tuples[response_num][1] human_name = notification_tuples[response_num][2] - url = _reverse_with_slash(url_name, course_id) + url = _reverse_without_slash(url_name, course_id) has_img = response[tag] img_path = "/static/images/slider-handle.png" diff --git a/lms/templates/open_ended_problems/combined_notifications.html b/lms/templates/open_ended_problems/combined_notifications.html index d14b2b9d9c..6d8541b567 100644 --- a/lms/templates/open_ended_problems/combined_notifications.html +++ b/lms/templates/open_ended_problems/combined_notifications.html @@ -27,7 +27,7 @@ %for notification in notification_list:
  • - {notification['name']} + ${notification['name']} % if notification['has_img'] == True: %endif From 457afaaedab2f57fe7a872b5ef37ebc2323c7e75 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:14:28 -0500 Subject: [PATCH 036/153] Making tab render properly for open ended notifications --- lms/djangoapps/courseware/tabs.py | 1 + lms/djangoapps/open_ended_grading/views.py | 19 ++++++++++++++++++- .../open_ended_problems.html | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 3aa502e95e..b1e56ead08 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -181,6 +181,7 @@ VALID_TAB_TYPES = { 'static_tab': TabImpl(key_checker(['name', 'url_slug']), _static_tab), 'peer_grading': TabImpl(null_validator, _peer_grading), 'staff_grading': TabImpl(null_validator, _staff_grading), + 'open_ended': TabImpl(null_validator, _combined_open_ended_grading), } diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 84bf14bf0e..29c3c3ebd3 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -22,6 +22,9 @@ from student.models import unique_id_for_user import open_ended_util import open_ended_notifications +from xmodule.modulestore.django import modulestore +from xmodule.modulestore import search + log = logging.getLogger(__name__) template_imports = {'urllib': urllib} @@ -137,6 +140,8 @@ def student_problem_list(request, course_id): success = False error_text = "" problem_list = [] + base_course_url = reverse('courses') + try: problem_list_json = controller_qs.get_grading_status_list(course_id, unique_id_for_user(request.user)) problem_list_dict = json.loads(problem_list_json) @@ -146,6 +151,19 @@ def student_problem_list(request, course_id): problem_list = problem_list_dict['problem_list'] + for i in xrange(0,len(problem_list)): + problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location']) + log.debug(problem_url_parts) + problem_url = base_course_url + "/" + for z in xrange(0,len(problem_url_parts)): + part = problem_url_parts[z] + if part is not None: + if z==1: + problem_url += "courseware/" + problem_url += part + "/" + + problem_list[i].update({'actual_url' : problem_url}) + except GradingServiceError: error_text = "Error occured while contacting the grading service" success = False @@ -171,7 +189,6 @@ def combined_notifications(request, course_id): course = get_course_with_access(request.user, course_id, 'load') user = request.user notifications = open_ended_notifications.combined_notifications(course, user) - log.debug(notifications) response = notifications['response'] notification_tuples=open_ended_notifications.NOTIFICATION_TYPES diff --git a/lms/templates/open_ended_problems/open_ended_problems.html b/lms/templates/open_ended_problems/open_ended_problems.html index ec648afab8..da309664eb 100644 --- a/lms/templates/open_ended_problems/open_ended_problems.html +++ b/lms/templates/open_ended_problems/open_ended_problems.html @@ -26,7 +26,7 @@ From d3d4e557d69da545f26d20d724a46853056295ec Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:16:18 -0500 Subject: [PATCH 037/153] Fix notification display --- lms/djangoapps/courseware/tabs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index b1e56ead08..7224657b60 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -132,8 +132,8 @@ def _peer_grading(tab, user, course, active_page): def _combined_open_ended_grading(tab, user, course, active_page): if user.is_authenticated: - link = reverse('open_ended_problems', args=[course.id]) - tab_name = "Open Ended Questions" + link = reverse('open_ended_notifications', args=[course.id]) + tab_name = "Open Ended Panel" notifications = open_ended_notifications.combined_notifications(course, user) pending_grading = notifications['pending_grading'] From bb55792b2bbf333812f0a3f4fbfdd9cd082102f7 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:21:17 -0500 Subject: [PATCH 038/153] Make things less noisy (remove debug statements) --- lms/djangoapps/open_ended_grading/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 29c3c3ebd3..55f4907687 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -153,7 +153,6 @@ def student_problem_list(request, course_id): for i in xrange(0,len(problem_list)): problem_url_parts = search.path_to_location(modulestore(), course.id, problem_list[i]['location']) - log.debug(problem_url_parts) problem_url = base_course_url + "/" for z in xrange(0,len(problem_url_parts)): part = problem_url_parts[z] @@ -219,8 +218,6 @@ def combined_notifications(request, course_id): 'ajax_url' : ajax_url, } - log.debug(combined_dict) - return render_to_response('open_ended_problems/combined_notifications.html', combined_dict ) From 6412eb40809ed2140192a2764abfd9556d489471 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 10:19:34 -0500 Subject: [PATCH 039/153] Fix naming for overall need to check --- lms/djangoapps/courseware/tabs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index 7224657b60..a53e4bfb0a 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -107,6 +107,7 @@ def _textbooks(tab, user, course, active_page): def _staff_grading(tab, user, course, active_page): if has_access(user, course, 'staff'): link = reverse('staff_grading', args=[course.id]) + tab_name = "Staff grading" notifications = open_ended_notifications.staff_grading_notifications(course) @@ -118,6 +119,7 @@ def _staff_grading(tab, user, course, active_page): return [] def _peer_grading(tab, user, course, active_page): + if user.is_authenticated(): link = reverse('peer_grading', args=[course.id]) tab_name = "Peer grading" From 0630f59c06bba9c75d6ba8cef9ac75e2ad7e5c69 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 13:13:57 -0500 Subject: [PATCH 040/153] Pass proper parameters, fix templating --- lms/djangoapps/open_ended_grading/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index 55f4907687..ad59262536 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -188,6 +188,7 @@ def combined_notifications(request, course_id): course = get_course_with_access(request.user, course_id, 'load') user = request.user notifications = open_ended_notifications.combined_notifications(course, user) + log.debug(notifications) response = notifications['response'] notification_tuples=open_ended_notifications.NOTIFICATION_TYPES @@ -218,6 +219,8 @@ def combined_notifications(request, course_id): 'ajax_url' : ajax_url, } + log.debug(combined_dict) + return render_to_response('open_ended_problems/combined_notifications.html', combined_dict ) From 33003e65d443cd6d36c0de6bceb045a035160cf1 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:21:17 -0500 Subject: [PATCH 041/153] Make things less noisy (remove debug statements) --- lms/djangoapps/open_ended_grading/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/views.py b/lms/djangoapps/open_ended_grading/views.py index ad59262536..931cbb7337 100644 --- a/lms/djangoapps/open_ended_grading/views.py +++ b/lms/djangoapps/open_ended_grading/views.py @@ -219,8 +219,6 @@ def combined_notifications(request, course_id): 'ajax_url' : ajax_url, } - log.debug(combined_dict) - return render_to_response('open_ended_problems/combined_notifications.html', combined_dict ) From 5291271591e8eb856517b6c3f13e893509fb51db Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:33:18 -0500 Subject: [PATCH 042/153] Add in some minimal checking to make sure that the max score and scores for each rubric point are 3 --- .../lib/xmodule/xmodule/combined_open_ended_module.py | 10 +++++----- 1 file changed, 5 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 aa4b1f18ad..98018e5d80 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -141,11 +141,11 @@ class CombinedOpenEndedModule(XModule): self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) rubric_renderer = CombinedOpenEndedRubric(system, True) - try: - rubric_feedback = rubric_renderer.render_rubric(stringify_children(definition['rubric'])) - except RubricParsingError: - log.error("Failed to parse rubric in location: {1}".format(location)) - raise + success, rubric_feedback = rubric_renderer.render_rubric(stringify_children(definition['rubric'])) + if not success: + error_message="Could not parse rubric : {0}".format(definition['rubric']) + log.exception(error_message) + raise Exception #Static data is passed to the child modules to render self.static_data = { 'max_score': self._max_score, From 29f738f7ce3480dd17f971fb4a7dc9f785cfb9ac Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 14:34:08 -0500 Subject: [PATCH 043/153] Minor count fix --- .../xmodule/combined_open_ended_module.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/lib/xmodule/xmodule/combined_open_ended_module.py b/common/lib/xmodule/xmodule/combined_open_ended_module.py index 98018e5d80..53bfcf7673 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -35,6 +35,9 @@ MAX_ATTEMPTS = 10000 # Overriden by max_score specified in xml. MAX_SCORE = 1 +#The highest score allowed for the overall xmodule and for each rubric point +MAX_SCORE_ALLOWED = 3 + class CombinedOpenEndedModule(XModule): """ This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc). @@ -140,12 +143,25 @@ class CombinedOpenEndedModule(XModule): # completion (doesn't matter if you self-assessed correct/incorrect). self._max_score = int(self.metadata.get('max_score', MAX_SCORE)) + if self._max_score>MAX_SCORE_ALLOWED: + error_message="Max score {0} is higher than max score allowed {1}".format(self._max_score, MAX_SCORE_ALLOWED) + log.exception(error_message) + raise Exception + rubric_renderer = CombinedOpenEndedRubric(system, True) success, rubric_feedback = rubric_renderer.render_rubric(stringify_children(definition['rubric'])) if not success: error_message="Could not parse rubric : {0}".format(definition['rubric']) log.exception(error_message) raise Exception + + rubric_categories = rubric_renderer.extract_categories(stringify_children(definition['rubric'])) + for category in rubric_categories: + if len(category['options'])>MAX_SCORE_ALLOWED: + error_message="Number of score points in rubric higher than the max allowed, which is {0} : {1}".format(MAX_SCORE_ALLOWED, definition['rubric']) + log.exception(error_message) + raise Exception + #Static data is passed to the child modules to render self.static_data = { 'max_score': self._max_score, From 3531bce7d3c0726d2bcf0ad4bbf0fa357768eff0 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 15:10:12 -0500 Subject: [PATCH 044/153] Fix score checking, latest post assessment scraping --- common/lib/xmodule/xmodule/combined_open_ended_module.py | 4 ++-- 1 file changed, 2 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 53bfcf7673..df8efc30c6 100644 --- a/common/lib/xmodule/xmodule/combined_open_ended_module.py +++ b/common/lib/xmodule/xmodule/combined_open_ended_module.py @@ -157,8 +157,8 @@ class CombinedOpenEndedModule(XModule): rubric_categories = rubric_renderer.extract_categories(stringify_children(definition['rubric'])) for category in rubric_categories: - if len(category['options'])>MAX_SCORE_ALLOWED: - error_message="Number of score points in rubric higher than the max allowed, which is {0} : {1}".format(MAX_SCORE_ALLOWED, definition['rubric']) + if len(category['options'])>(MAX_SCORE_ALLOWED+1): + error_message="Number of score points in rubric {0} higher than the max allowed, which is {1}".format(len(category['options']) , MAX_SCORE_ALLOWED) log.exception(error_message) raise Exception From 8b8c1dbe60b71997e2149ac882fe9c576738f715 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 17:29:09 -0500 Subject: [PATCH 045/153] Fix query error --- .../open_ended_grading/open_ended_notifications.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index a5faef1011..3529e124b4 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -66,9 +66,14 @@ def combined_notifications(course, user): course_id = course.id min_time_to_query = user.last_login - last_module_seen = StudentModule.objects.filter(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified')[0] + last_module_seen = StudentModule.objects.filter(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified') + last_module_seen_count = last_module_seen.count() + + if last_module_seen_count>0: + last_time_viewed = last_module_seen[0]['modified'] + else: + last_time_viewed = user.last_login - last_time_viewed = last_module_seen['modified'] pending_grading= False img_path= "" From 68f563a178e09be74aa0cd447972aa4d2e94ba77 Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Tue, 22 Jan 2013 17:35:40 -0500 Subject: [PATCH 046/153] Fix naming issue --- lms/djangoapps/open_ended_grading/open_ended_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index 3529e124b4..d6621e468a 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -14,7 +14,7 @@ log=logging.getLogger(__name__) NOTIFICATION_TYPES = ( ('student_needs_to_peer_grade', 'peer_grading', 'Peer Grading'), ('staff_needs_to_grade', 'staff_grading', 'Staff Grading'), - ('overall_need_to_check', 'open_ended_problems', 'Problems you have submitted') + ('new_student_grading_to_view', 'open_ended_problems', 'Problems you have submitted') ) def staff_grading_notifications(course): From 665fb707b1f6c81c645c49d4efced853d7bb25fc Mon Sep 17 00:00:00 2001 From: Vik Paruchuri Date: Wed, 23 Jan 2013 10:43:45 -0500 Subject: [PATCH 047/153] Cache notifications for 5 mins so that we don't hit the grading server on every web request --- lms/djangoapps/courseware/tabs.py | 2 +- .../open_ended_notifications.py | 72 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py index a53e4bfb0a..b52324b360 100644 --- a/lms/djangoapps/courseware/tabs.py +++ b/lms/djangoapps/courseware/tabs.py @@ -110,7 +110,7 @@ def _staff_grading(tab, user, course, active_page): tab_name = "Staff grading" - notifications = open_ended_notifications.staff_grading_notifications(course) + notifications = open_ended_notifications.staff_grading_notifications(course, user) pending_grading = notifications['pending_grading'] img_path = notifications['img_path'] diff --git a/lms/djangoapps/open_ended_grading/open_ended_notifications.py b/lms/djangoapps/open_ended_grading/open_ended_notifications.py index d6621e468a..80f17352e9 100644 --- a/lms/djangoapps/open_ended_grading/open_ended_notifications.py +++ b/lms/djangoapps/open_ended_grading/open_ended_notifications.py @@ -8,20 +8,31 @@ import open_ended_util from courseware.models import StudentModule import logging from courseware.access import has_access +from util.cache import cache log=logging.getLogger(__name__) +NOTIFICATION_CACHE_TIME = 300 +KEY_PREFIX = "open_ended_" + NOTIFICATION_TYPES = ( ('student_needs_to_peer_grade', 'peer_grading', 'Peer Grading'), ('staff_needs_to_grade', 'staff_grading', 'Staff Grading'), ('new_student_grading_to_view', 'open_ended_problems', 'Problems you have submitted') ) -def staff_grading_notifications(course): +def staff_grading_notifications(course, user): staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE) pending_grading=False img_path= "" course_id = course.id + student_id = unique_id_for_user(user) + notification_type = "staff" + + success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) + if success: + return notification_dict + try: notifications = json.loads(staff_gs.get_notifications(course_id)) if notifications['success']: @@ -35,16 +46,26 @@ def staff_grading_notifications(course): if pending_grading: img_path = "/static/images/slider-handle.png" - return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + + set_value_in_cache(student_id, course_id, notification_type, notification_dict) + + return notification_dict def peer_grading_notifications(course, user): peer_gs = PeerGradingService(settings.PEER_GRADING_INTERFACE) pending_grading=False img_path= "" course_id = course.id + student_id = unique_id_for_user(user) + notification_type = "peer" + + success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) + if success: + return notification_dict try: - notifications = json.loads(peer_gs.get_notifications(course_id,unique_id_for_user(user))) + notifications = json.loads(peer_gs.get_notifications(course_id,student_id)) if notifications['success']: if notifications['student_needs_to_peer_grade']: pending_grading=True @@ -56,7 +77,11 @@ def peer_grading_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + + set_value_in_cache(student_id, course_id, notification_type, notification_dict) + + return notification_dict def combined_notifications(course, user): controller_url = open_ended_util.get_controller_url() @@ -64,6 +89,11 @@ def combined_notifications(course, user): student_id = unique_id_for_user(user) user_is_staff = has_access(user, course, 'staff') course_id = course.id + notification_type = "combined" + + success, notification_dict = get_value_from_cache(student_id, course_id, notification_type) + if success: + return notification_dict min_time_to_query = user.last_login last_module_seen = StudentModule.objects.filter(student=user, course_id = course_id, modified__gt=min_time_to_query).values('modified').order_by('-modified') @@ -92,4 +122,36 @@ def combined_notifications(course, user): if pending_grading: img_path = "/static/images/slider-handle.png" - return {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} \ No newline at end of file + notification_dict = {'pending_grading' : pending_grading, 'img_path' : img_path, 'response' : notifications} + + set_value_in_cache(student_id, course_id, notification_type, notification_dict) + + return notification_dict + +def get_value_from_cache(student_id, course_id, notification_type): + key_name = create_key_name(student_id, course_id, notification_type) + success, value = _get_value_from_cache(key_name) + return success, value + +def set_value_in_cache(student_id, course_id, notification_type, value): + key_name = create_key_name(student_id, course_id, notification_type) + _set_value_in_cache(key_name, value) + +def create_key_name(student_id, course_id, notification_type): + key_name = "{prefix}{type}_{course}_{student}".format(prefix=KEY_PREFIX, type=notification_type, course=course_id, student=student_id) + return key_name + +def _get_value_from_cache(key_name): + value = cache.get(key_name) + success = False + if value is None: + return success , value + try: + value = json.loads(value) + success = True + except: + pass + return success , value + +def _set_value_in_cache(key_name, value): + cache.set(key_name, json.dumps(value), NOTIFICATION_CACHE_TIME) \ No newline at end of file From d07bf0157e41cde06d28722832999c99273c9c28 Mon Sep 17 00:00:00 2001 From: Diana Huang Date: Wed, 23 Jan 2013 10:55:30 -0500 Subject: [PATCH 048/153] UI updates to the Open Ended Problems page --- lms/static/sass/course.scss | 2 ++ .../sass/course/_open_ended_grading.scss | 17 +++++++++++++ .../open_ended_problems.html | 25 ++++++++++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 lms/static/sass/course/_open_ended_grading.scss diff --git a/lms/static/sass/course.scss b/lms/static/sass/course.scss index e900e589b2..17406782fe 100644 --- a/lms/static/sass/course.scss +++ b/lms/static/sass/course.scss @@ -44,6 +44,8 @@ @import "course/gradebook"; @import "course/tabs"; @import "course/staff_grading"; +@import "course/open_ended_grading"; + // instructor @import "course/instructor/instructor"; diff --git a/lms/static/sass/course/_open_ended_grading.scss b/lms/static/sass/course/_open_ended_grading.scss new file mode 100644 index 0000000000..86ab5f4aa0 --- /dev/null +++ b/lms/static/sass/course/_open_ended_grading.scss @@ -0,0 +1,17 @@ +.open-ended-problems, +.combined-notifications +{ + padding: 40px; + .problem-list + { + table-layout: auto; + margin-top: 10px; + width:70%; + text-align: center; + td, th + { + padding: 7px; + } + } + +} diff --git a/lms/templates/open_ended_problems/open_ended_problems.html b/lms/templates/open_ended_problems/open_ended_problems.html index da309664eb..f891947079 100644 --- a/lms/templates/open_ended_problems/open_ended_problems.html +++ b/lms/templates/open_ended_problems/open_ended_problems.html @@ -23,14 +23,27 @@ You have not attempted any open ended problems yet. %else: -