Commit 00abaffe by Mark L. Chang

Merge remote-tracking branch 'origin/master' into feature/markchang/studio-analytics

parents db1c0bf0 1b7e552d
...@@ -655,9 +655,9 @@ class MatlabInput(CodeInput): ...@@ -655,9 +655,9 @@ class MatlabInput(CodeInput):
# Check if problem has been queued # Check if problem has been queued
self.queuename = 'matlab' self.queuename = 'matlab'
self.queue_msg = '' self.queue_msg = ''
if 'queue_msg' in self.input_state and self.status in ['queued','incomplete', 'unsubmitted']: if 'queue_msg' in self.input_state and self.status in ['queued', 'incomplete', 'unsubmitted']:
self.queue_msg = self.input_state['queue_msg'] self.queue_msg = self.input_state['queue_msg']
if 'queued' in self.input_state and self.input_state['queuestate'] is not None: if 'queuestate' in self.input_state and self.input_state['queuestate'] == 'queued':
self.status = 'queued' self.status = 'queued'
self.queue_len = 1 self.queue_len = 1
self.msg = self.plot_submitted_msg self.msg = self.plot_submitted_msg
...@@ -702,7 +702,7 @@ class MatlabInput(CodeInput): ...@@ -702,7 +702,7 @@ class MatlabInput(CodeInput):
def _extra_context(self): def _extra_context(self):
''' Set up additional context variables''' ''' Set up additional context variables'''
extra_context = { extra_context = {
'queue_len': self.queue_len, 'queue_len': str(self.queue_len),
'queue_msg': self.queue_msg 'queue_msg': self.queue_msg
} }
return extra_context return extra_context
......
...@@ -361,7 +361,6 @@ class MatlabTest(unittest.TestCase): ...@@ -361,7 +361,6 @@ class MatlabTest(unittest.TestCase):
'feedback': {'message': '3'}, } 'feedback': {'message': '3'}, }
elt = etree.fromstring(self.xml) elt = etree.fromstring(self.xml)
input_class = lookup_tag('matlabinput')
the_input = self.input_class(test_system, elt, state) the_input = self.input_class(test_system, elt, state)
context = the_input._get_render_context() context = the_input._get_render_context()
...@@ -381,6 +380,31 @@ class MatlabTest(unittest.TestCase): ...@@ -381,6 +380,31 @@ class MatlabTest(unittest.TestCase):
self.assertEqual(context, expected) self.assertEqual(context, expected)
def test_rendering_while_queued(self):
state = {'value': 'print "good evening"',
'status': 'incomplete',
'input_state': {'queuestate': 'queued'},
}
elt = etree.fromstring(self.xml)
the_input = self.input_class(test_system, elt, state)
context = the_input._get_render_context()
expected = {'id': 'prob_1_2',
'value': 'print "good evening"',
'status': 'queued',
'msg': self.input_class.plot_submitted_msg,
'mode': self.mode,
'rows': self.rows,
'cols': self.cols,
'queue_msg': '',
'linenumbers': 'true',
'hidden': '',
'tabsize': int(self.tabsize),
'queue_len': '1',
}
self.assertEqual(context, expected)
def test_plot_data(self): def test_plot_data(self):
get = {'submission': 'x = 1234;'} get = {'submission': 'x = 1234;'}
response = self.the_input.handle_ajax("plot", get) response = self.the_input.handle_ajax("plot", get)
...@@ -391,6 +415,43 @@ class MatlabTest(unittest.TestCase): ...@@ -391,6 +415,43 @@ class MatlabTest(unittest.TestCase):
self.assertTrue(self.the_input.input_state['queuekey'] is not None) self.assertTrue(self.the_input.input_state['queuekey'] is not None)
self.assertEqual(self.the_input.input_state['queuestate'], 'queued') self.assertEqual(self.the_input.input_state['queuestate'], 'queued')
def test_ungraded_response_success(self):
queuekey = 'abcd'
input_state = {'queuekey': queuekey, 'queuestate': 'queued'}
state = {'value': 'print "good evening"',
'status': 'incomplete',
'input_state': input_state,
'feedback': {'message': '3'}, }
elt = etree.fromstring(self.xml)
the_input = self.input_class(test_system, elt, state)
inner_msg = 'hello!'
queue_msg = json.dumps({'msg': inner_msg})
the_input.ungraded_response(queue_msg, queuekey)
self.assertTrue(input_state['queuekey'] is None)
self.assertTrue(input_state['queuestate'] is None)
self.assertEqual(input_state['queue_msg'], inner_msg)
def test_ungraded_response_key_mismatch(self):
queuekey = 'abcd'
input_state = {'queuekey': queuekey, 'queuestate': 'queued'}
state = {'value': 'print "good evening"',
'status': 'incomplete',
'input_state': input_state,
'feedback': {'message': '3'}, }
elt = etree.fromstring(self.xml)
the_input = self.input_class(test_system, elt, state)
inner_msg = 'hello!'
queue_msg = json.dumps({'msg': inner_msg})
the_input.ungraded_response(queue_msg, 'abc')
self.assertEqual(input_state['queuekey'], queuekey)
self.assertEqual(input_state['queuestate'], 'queued')
self.assertFalse('queue_msg' in input_state)
......
...@@ -108,11 +108,10 @@ class CapaModule(CapaFields, XModule): ...@@ -108,11 +108,10 @@ class CapaModule(CapaFields, XModule):
''' '''
icon_class = 'problem' icon_class = 'problem'
js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'), js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'),
resource_string(__name__, 'js/src/javascript_loader.coffee'), resource_string(__name__, 'js/src/javascript_loader.coffee'),
], ],
'js': [resource_string(__name__, 'js/src/capa/imageinput.js'), 'js': [resource_string(__name__, 'js/src/capa/imageinput.js'),
resource_string(__name__, 'js/src/capa/schematic.js') resource_string(__name__, 'js/src/capa/schematic.js')
]} ]}
...@@ -367,11 +366,11 @@ class CapaModule(CapaFields, XModule): ...@@ -367,11 +366,11 @@ class CapaModule(CapaFields, XModule):
self.set_state_from_lcp() self.set_state_from_lcp()
# Prepend a scary warning to the student # Prepend a scary warning to the student
warning = '<div class="capa_reset">'\ warning = '<div class="capa_reset">'\
'<h2>Warning: The problem has been reset to its initial state!</h2>'\ '<h2>Warning: The problem has been reset to its initial state!</h2>'\
'The problem\'s state was corrupted by an invalid submission. ' \ 'The problem\'s state was corrupted by an invalid submission. ' \
'The submission consisted of:'\ 'The submission consisted of:'\
'<ul>' '<ul>'
for student_answer in student_answers.values(): for student_answer in student_answers.values():
if student_answer != '': if student_answer != '':
warning += '<li>' + cgi.escape(student_answer) + '</li>' warning += '<li>' + cgi.escape(student_answer) + '</li>'
...@@ -388,7 +387,6 @@ class CapaModule(CapaFields, XModule): ...@@ -388,7 +387,6 @@ class CapaModule(CapaFields, XModule):
return html return html
def get_problem_html(self, encapsulate=True): def get_problem_html(self, encapsulate=True):
'''Return html for the problem. Adds check, reset, save buttons '''Return html for the problem. Adds check, reset, save buttons
as necessary based on the problem config and state.''' as necessary based on the problem config and state.'''
...@@ -401,7 +399,6 @@ class CapaModule(CapaFields, XModule): ...@@ -401,7 +399,6 @@ class CapaModule(CapaFields, XModule):
except Exception, err: except Exception, err:
html = self.handle_problem_html_error(err) html = self.handle_problem_html_error(err)
# The convention is to pass the name of the check button # The convention is to pass the name of the check button
# if we want to show a check button, and False otherwise # if we want to show a check button, and False otherwise
# This works because non-empty strings evaluate to True # This works because non-empty strings evaluate to True
...@@ -454,7 +451,7 @@ class CapaModule(CapaFields, XModule): ...@@ -454,7 +451,7 @@ class CapaModule(CapaFields, XModule):
'score_update': self.update_score, 'score_update': self.update_score,
'input_ajax': self.handle_input_ajax, 'input_ajax': self.handle_input_ajax,
'ungraded_response': self.handle_ungraded_response 'ungraded_response': self.handle_ungraded_response
} }
if dispatch not in handlers: if dispatch not in handlers:
return 'Error' return 'Error'
...@@ -472,7 +469,7 @@ class CapaModule(CapaFields, XModule): ...@@ -472,7 +469,7 @@ class CapaModule(CapaFields, XModule):
d.update({ d.update({
'progress_changed': after != before, 'progress_changed': after != before,
'progress_status': Progress.to_js_status_str(after), 'progress_status': Progress.to_js_status_str(after),
}) })
return json.dumps(d, cls=ComplexEncoder) return json.dumps(d, cls=ComplexEncoder)
def is_past_due(self): def is_past_due(self):
...@@ -535,7 +532,6 @@ class CapaModule(CapaFields, XModule): ...@@ -535,7 +532,6 @@ class CapaModule(CapaFields, XModule):
return False return False
def update_score(self, get): def update_score(self, get):
""" """
Delivers grading response (e.g. from asynchronous code checking) to Delivers grading response (e.g. from asynchronous code checking) to
...@@ -590,7 +586,6 @@ class CapaModule(CapaFields, XModule): ...@@ -590,7 +586,6 @@ class CapaModule(CapaFields, XModule):
self.set_state_from_lcp() self.set_state_from_lcp()
return response return response
def get_answer(self, get): def get_answer(self, get):
''' '''
For the "show answer" button. For the "show answer" button.
...@@ -700,7 +695,6 @@ class CapaModule(CapaFields, XModule): ...@@ -700,7 +695,6 @@ class CapaModule(CapaFields, XModule):
'max_value': score['total'], 'max_value': score['total'],
}) })
def check_problem(self, get): def check_problem(self, get):
''' Checks whether answers to a problem are correct, and ''' Checks whether answers to a problem are correct, and
returns a map of correct/incorrect answers: returns a map of correct/incorrect answers:
...@@ -783,7 +777,7 @@ class CapaModule(CapaFields, XModule): ...@@ -783,7 +777,7 @@ class CapaModule(CapaFields, XModule):
self.system.track_function('save_problem_check', event_info) self.system.track_function('save_problem_check', event_info)
if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback if hasattr(self.system, 'psychometrics_handler'): # update PsychometricsData using callback
self.system.psychometrics_handler(self.get_instance_state()) self.system.psychometrics_handler(self.get_state_for_lcp())
# render problem into HTML # render problem into HTML
html = self.get_problem_html(encapsulate=False) html = self.get_problem_html(encapsulate=False)
......
...@@ -35,6 +35,7 @@ class CapaFactory(object): ...@@ -35,6 +35,7 @@ class CapaFactory(object):
""" """
num = 0 num = 0
@staticmethod @staticmethod
def next_num(): def next_num():
CapaFactory.num += 1 CapaFactory.num += 1
...@@ -49,7 +50,7 @@ class CapaFactory(object): ...@@ -49,7 +50,7 @@ class CapaFactory(object):
def answer_key(): def answer_key():
""" Return the key stored in the capa problem answer dict """ """ Return the key stored in the capa problem answer dict """
return ("-".join(['i4x', 'edX', 'capa_test', 'problem', return ("-".join(['i4x', 'edX', 'capa_test', 'problem',
'SampleProblem%d' % CapaFactory.num]) + 'SampleProblem%d' % CapaFactory.num]) +
"_2_1") "_2_1")
@staticmethod @staticmethod
...@@ -120,7 +121,6 @@ class CapaFactory(object): ...@@ -120,7 +121,6 @@ class CapaFactory(object):
return module return module
class CapaModuleTest(unittest.TestCase): class CapaModuleTest(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -142,9 +142,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -142,9 +142,6 @@ class CapaModuleTest(unittest.TestCase):
self.assertNotEqual(module.url_name, other_module.url_name, self.assertNotEqual(module.url_name, other_module.url_name,
"Factory should be creating unique names for each problem") "Factory should be creating unique names for each problem")
def test_correct(self): def test_correct(self):
""" """
Check that the factory creates correct and incorrect problems properly. Check that the factory creates correct and incorrect problems properly.
...@@ -155,7 +152,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -155,7 +152,6 @@ class CapaModuleTest(unittest.TestCase):
other_module = CapaFactory.create(correct=True) other_module = CapaFactory.create(correct=True)
self.assertEqual(other_module.get_score()['score'], 1) self.assertEqual(other_module.get_score()['score'], 1)
def test_showanswer_default(self): def test_showanswer_default(self):
""" """
Make sure the show answer logic does the right thing. Make sure the show answer logic does the right thing.
...@@ -165,14 +161,12 @@ class CapaModuleTest(unittest.TestCase): ...@@ -165,14 +161,12 @@ class CapaModuleTest(unittest.TestCase):
problem = CapaFactory.create() problem = CapaFactory.create()
self.assertFalse(problem.answer_available()) self.assertFalse(problem.answer_available())
def test_showanswer_attempted(self): def test_showanswer_attempted(self):
problem = CapaFactory.create(showanswer='attempted') problem = CapaFactory.create(showanswer='attempted')
self.assertFalse(problem.answer_available()) self.assertFalse(problem.answer_available())
problem.attempts = 1 problem.attempts = 1
self.assertTrue(problem.answer_available()) self.assertTrue(problem.answer_available())
def test_showanswer_closed(self): def test_showanswer_closed(self):
# can see after attempts used up, even with due date in the future # can see after attempts used up, even with due date in the future
...@@ -182,21 +176,19 @@ class CapaModuleTest(unittest.TestCase): ...@@ -182,21 +176,19 @@ class CapaModuleTest(unittest.TestCase):
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertTrue(used_all_attempts.answer_available()) self.assertTrue(used_all_attempts.answer_available())
# can see after due date # can see after due date
after_due_date = CapaFactory.create(showanswer='closed', after_due_date = CapaFactory.create(showanswer='closed',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.yesterday_str) due=self.yesterday_str)
self.assertTrue(after_due_date.answer_available()) self.assertTrue(after_due_date.answer_available())
# can't see because attempts left # can't see because attempts left
attempts_left_open = CapaFactory.create(showanswer='closed', attempts_left_open = CapaFactory.create(showanswer='closed',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertFalse(attempts_left_open.answer_available()) self.assertFalse(attempts_left_open.answer_available())
# Can't see because grace period hasn't expired # Can't see because grace period hasn't expired
...@@ -207,8 +199,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -207,8 +199,6 @@ class CapaModuleTest(unittest.TestCase):
graceperiod=self.two_day_delta_str) graceperiod=self.two_day_delta_str)
self.assertFalse(still_in_grace.answer_available()) self.assertFalse(still_in_grace.answer_available())
def test_showanswer_past_due(self): def test_showanswer_past_due(self):
""" """
With showanswer="past_due" should only show answer after the problem is closed With showanswer="past_due" should only show answer after the problem is closed
...@@ -222,20 +212,18 @@ class CapaModuleTest(unittest.TestCase): ...@@ -222,20 +212,18 @@ class CapaModuleTest(unittest.TestCase):
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertFalse(used_all_attempts.answer_available()) self.assertFalse(used_all_attempts.answer_available())
# can see after due date # can see after due date
past_due_date = CapaFactory.create(showanswer='past_due', past_due_date = CapaFactory.create(showanswer='past_due',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.yesterday_str) due=self.yesterday_str)
self.assertTrue(past_due_date.answer_available()) self.assertTrue(past_due_date.answer_available())
# can't see because attempts left # can't see because attempts left
attempts_left_open = CapaFactory.create(showanswer='past_due', attempts_left_open = CapaFactory.create(showanswer='past_due',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertFalse(attempts_left_open.answer_available()) self.assertFalse(attempts_left_open.answer_available())
# Can't see because grace period hasn't expired, even though have no more # Can't see because grace period hasn't expired, even though have no more
...@@ -260,31 +248,28 @@ class CapaModuleTest(unittest.TestCase): ...@@ -260,31 +248,28 @@ class CapaModuleTest(unittest.TestCase):
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertTrue(used_all_attempts.answer_available()) self.assertTrue(used_all_attempts.answer_available())
# can see after due date # can see after due date
past_due_date = CapaFactory.create(showanswer='finished', past_due_date = CapaFactory.create(showanswer='finished',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.yesterday_str) due=self.yesterday_str)
self.assertTrue(past_due_date.answer_available()) self.assertTrue(past_due_date.answer_available())
# can't see because attempts left and wrong # can't see because attempts left and wrong
attempts_left_open = CapaFactory.create(showanswer='finished', attempts_left_open = CapaFactory.create(showanswer='finished',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.tomorrow_str) due=self.tomorrow_str)
self.assertFalse(attempts_left_open.answer_available()) self.assertFalse(attempts_left_open.answer_available())
# _can_ see because attempts left and right # _can_ see because attempts left and right
correct_ans = CapaFactory.create(showanswer='finished', correct_ans = CapaFactory.create(showanswer='finished',
max_attempts="1", max_attempts="1",
attempts="0", attempts="0",
due=self.tomorrow_str, due=self.tomorrow_str,
correct=True) correct=True)
self.assertTrue(correct_ans.answer_available()) self.assertTrue(correct_ans.answer_available())
# Can see even though grace period hasn't expired, because have no more # Can see even though grace period hasn't expired, because have no more
# attempts. # attempts.
still_in_grace = CapaFactory.create(showanswer='finished', still_in_grace = CapaFactory.create(showanswer='finished',
...@@ -294,7 +279,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -294,7 +279,6 @@ class CapaModuleTest(unittest.TestCase):
graceperiod=self.two_day_delta_str) graceperiod=self.two_day_delta_str)
self.assertTrue(still_in_grace.answer_available()) self.assertTrue(still_in_grace.answer_available())
def test_closed(self): def test_closed(self):
# Attempts < Max attempts --> NOT closed # Attempts < Max attempts --> NOT closed
...@@ -322,7 +306,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -322,7 +306,6 @@ class CapaModuleTest(unittest.TestCase):
due=self.yesterday_str) due=self.yesterday_str)
self.assertTrue(module.closed()) self.assertTrue(module.closed())
def test_parse_get_params(self): def test_parse_get_params(self):
# We have to set up Django settings in order to use QueryDict # We have to set up Django settings in order to use QueryDict
...@@ -348,7 +331,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -348,7 +331,6 @@ class CapaModuleTest(unittest.TestCase):
"Output dict should have key %s" % original_key) "Output dict should have key %s" % original_key)
self.assertEqual(valid_get_dict[original_key], result[key]) self.assertEqual(valid_get_dict[original_key], result[key])
# Valid GET param dict with list keys # Valid GET param dict with list keys
valid_get_dict = self._querydict_from_dict({'input_2[]': ['test1', 'test2']}) valid_get_dict = self._querydict_from_dict({'input_2[]': ['test1', 'test2']})
result = CapaModule.make_dict_of_responses(valid_get_dict) result = CapaModule.make_dict_of_responses(valid_get_dict)
...@@ -366,12 +348,11 @@ class CapaModuleTest(unittest.TestCase): ...@@ -366,12 +348,11 @@ class CapaModuleTest(unittest.TestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
result = CapaModule.make_dict_of_responses(invalid_get_dict) result = CapaModule.make_dict_of_responses(invalid_get_dict)
# Two equivalent names (one list, one non-list) # Two equivalent names (one list, one non-list)
# One of the values would overwrite the other, so detect this # One of the values would overwrite the other, so detect this
# and raise an exception # and raise an exception
invalid_get_dict = self._querydict_from_dict({'input_1[]': 'test 1', invalid_get_dict = self._querydict_from_dict({'input_1[]': 'test 1',
'input_1': 'test 2'}) 'input_1': 'test 2'})
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
result = CapaModule.make_dict_of_responses(invalid_get_dict) result = CapaModule.make_dict_of_responses(invalid_get_dict)
...@@ -395,7 +376,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -395,7 +376,6 @@ class CapaModuleTest(unittest.TestCase):
return copyDict return copyDict
def test_check_problem_correct(self): def test_check_problem_correct(self):
module = CapaFactory.create(attempts=1) module = CapaFactory.create(attempts=1)
...@@ -403,6 +383,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -403,6 +383,7 @@ class CapaModuleTest(unittest.TestCase):
# Simulate that all answers are marked correct, no matter # Simulate that all answers are marked correct, no matter
# what the input is, by patching CorrectMap.is_correct() # what the input is, by patching CorrectMap.is_correct()
# Also simulate rendering the HTML # Also simulate rendering the HTML
# TODO: pep8 thinks the following line has invalid syntax
with patch('capa.correctmap.CorrectMap.is_correct') as mock_is_correct,\ with patch('capa.correctmap.CorrectMap.is_correct') as mock_is_correct,\
patch('xmodule.capa_module.CapaModule.get_problem_html') as mock_html: patch('xmodule.capa_module.CapaModule.get_problem_html') as mock_html:
mock_is_correct.return_value = True mock_is_correct.return_value = True
...@@ -439,7 +420,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -439,7 +420,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the number of attempts is incremented by 1 # Expect that the number of attempts is incremented by 1
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_check_problem_closed(self): def test_check_problem_closed(self):
module = CapaFactory.create(attempts=3) module = CapaFactory.create(attempts=3)
...@@ -503,12 +483,11 @@ class CapaModuleTest(unittest.TestCase): ...@@ -503,12 +483,11 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the number of attempts is NOT incremented # Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_check_problem_error(self): def test_check_problem_error(self):
# Try each exception that capa_module should handle # Try each exception that capa_module should handle
for exception_class in [StudentInputError, for exception_class in [StudentInputError,
LoncapaProblemError, LoncapaProblemError,
ResponseError]: ResponseError]:
# Create the module # Create the module
...@@ -532,9 +511,9 @@ class CapaModuleTest(unittest.TestCase): ...@@ -532,9 +511,9 @@ class CapaModuleTest(unittest.TestCase):
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_check_problem_error_with_staff_user(self): def test_check_problem_error_with_staff_user(self):
# Try each exception that capa module should handle # Try each exception that capa module should handle
for exception_class in [StudentInputError, for exception_class in [StudentInputError,
LoncapaProblemError, LoncapaProblemError,
ResponseError]: ResponseError]:
...@@ -560,7 +539,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -560,7 +539,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the number of attempts is NOT incremented # Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_reset_problem(self): def test_reset_problem(self):
module = CapaFactory.create(done=True) module = CapaFactory.create(done=True)
module.new_lcp = Mock(wraps=module.new_lcp) module.new_lcp = Mock(wraps=module.new_lcp)
...@@ -583,7 +561,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -583,7 +561,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the problem was reset # Expect that the problem was reset
module.new_lcp.assert_called_once_with({'seed': None}) module.new_lcp.assert_called_once_with({'seed': None})
def test_reset_problem_closed(self): def test_reset_problem_closed(self):
module = CapaFactory.create() module = CapaFactory.create()
...@@ -598,7 +575,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -598,7 +575,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the problem was NOT reset # Expect that the problem was NOT reset
self.assertTrue('success' in result and not result['success']) self.assertTrue('success' in result and not result['success'])
def test_reset_problem_not_done(self): def test_reset_problem_not_done(self):
# Simulate that the problem is NOT done # Simulate that the problem is NOT done
module = CapaFactory.create(done=False) module = CapaFactory.create(done=False)
...@@ -610,7 +586,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -610,7 +586,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the problem was NOT reset # Expect that the problem was NOT reset
self.assertTrue('success' in result and not result['success']) self.assertTrue('success' in result and not result['success'])
def test_save_problem(self): def test_save_problem(self):
module = CapaFactory.create(done=False) module = CapaFactory.create(done=False)
...@@ -625,7 +600,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -625,7 +600,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the result is success # Expect that the result is success
self.assertTrue('success' in result and result['success']) self.assertTrue('success' in result and result['success'])
def test_save_problem_closed(self): def test_save_problem_closed(self):
module = CapaFactory.create(done=False) module = CapaFactory.create(done=False)
...@@ -640,7 +614,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -640,7 +614,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the result is failure # Expect that the result is failure
self.assertTrue('success' in result and not result['success']) self.assertTrue('success' in result and not result['success'])
def test_save_problem_submitted_with_randomize(self): def test_save_problem_submitted_with_randomize(self):
module = CapaFactory.create(rerandomize='always', done=True) module = CapaFactory.create(rerandomize='always', done=True)
...@@ -651,7 +624,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -651,7 +624,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that we cannot save # Expect that we cannot save
self.assertTrue('success' in result and not result['success']) self.assertTrue('success' in result and not result['success'])
def test_save_problem_submitted_no_randomize(self): def test_save_problem_submitted_no_randomize(self):
module = CapaFactory.create(rerandomize='never', done=True) module = CapaFactory.create(rerandomize='never', done=True)
...@@ -724,7 +696,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -724,7 +696,6 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(rerandomize="never", done=True) module = CapaFactory.create(rerandomize="never", done=True)
self.assertTrue(module.should_show_check_button()) self.assertTrue(module.should_show_check_button())
def test_should_show_reset_button(self): def test_should_show_reset_button(self):
attempts = random.randint(1, 10) attempts = random.randint(1, 10)
...@@ -755,7 +726,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -755,7 +726,6 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(max_attempts=0, done=True) module = CapaFactory.create(max_attempts=0, done=True)
self.assertTrue(module.should_show_reset_button()) self.assertTrue(module.should_show_reset_button())
def test_should_show_save_button(self): def test_should_show_save_button(self):
attempts = random.randint(1, 10) attempts = random.randint(1, 10)
...@@ -823,7 +793,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -823,7 +793,6 @@ class CapaModuleTest(unittest.TestCase):
html = module.get_problem_html() html = module.get_problem_html()
# assert that we got here without exploding # assert that we got here without exploding
def test_get_problem_html(self): def test_get_problem_html(self):
module = CapaFactory.create() module = CapaFactory.create()
...@@ -869,7 +838,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -869,7 +838,6 @@ class CapaModuleTest(unittest.TestCase):
# Assert that the encapsulated html contains the original html # Assert that the encapsulated html contains the original html
self.assertTrue(html in html_encapsulated) self.assertTrue(html in html_encapsulated)
def test_get_problem_html_error(self): def test_get_problem_html_error(self):
""" """
In production, when an error occurs with the problem HTML In production, when an error occurs with the problem HTML
...@@ -902,7 +870,6 @@ class CapaModuleTest(unittest.TestCase): ...@@ -902,7 +870,6 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the module has created a new dummy problem with the error # Expect that the module has created a new dummy problem with the error
self.assertNotEqual(original_problem, module.lcp) self.assertNotEqual(original_problem, module.lcp)
def test_random_seed_no_change(self): def test_random_seed_no_change(self):
# Run the test for each possible rerandomize value # Run the test for each possible rerandomize value
...@@ -920,10 +887,10 @@ class CapaModuleTest(unittest.TestCase): ...@@ -920,10 +887,10 @@ class CapaModuleTest(unittest.TestCase):
self.assertEqual(seed, 1) self.assertEqual(seed, 1)
# Check the problem # Check the problem
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
module.check_problem(get_request_dict) module.check_problem(get_request_dict)
# Expect that the seed is the same # Expect that the seed is the same
self.assertEqual(seed, module.seed) self.assertEqual(seed, module.seed)
# Save the problem # Save the problem
...@@ -933,7 +900,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -933,7 +900,7 @@ class CapaModuleTest(unittest.TestCase):
self.assertEqual(seed, module.seed) self.assertEqual(seed, module.seed)
def test_random_seed_with_reset(self): def test_random_seed_with_reset(self):
def _reset_and_get_seed(module): def _reset_and_get_seed(module):
''' '''
Reset the XModule and return the module's seed Reset the XModule and return the module's seed
...@@ -956,7 +923,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -956,7 +923,7 @@ class CapaModuleTest(unittest.TestCase):
Returns True if *test_func* was successful Returns True if *test_func* was successful
(returned True) within *num_tries* attempts (returned True) within *num_tries* attempts
*test_func* must be a function *test_func* must be a function
of the form test_func() -> bool of the form test_func() -> bool
''' '''
success = False success = False
...@@ -989,9 +956,10 @@ class CapaModuleTest(unittest.TestCase): ...@@ -989,9 +956,10 @@ class CapaModuleTest(unittest.TestCase):
# Since there's a small chance we might get the # Since there's a small chance we might get the
# same seed again, give it 5 chances # same seed again, give it 5 chances
# to generate a different seed # to generate a different seed
success = _retry_and_check(5, success = _retry_and_check(5,
lambda: _reset_and_get_seed(module) != seed) lambda: _reset_and_get_seed(module) != seed)
# TODO: change this comparison to module.seed is not None?
self.assertTrue(module.seed != None) self.assertTrue(module.seed != None)
msg = 'Could not get a new seed from reset after 5 tries' msg = 'Could not get a new seed from reset after 5 tries'
self.assertTrue(success, msg) self.assertTrue(success, msg)
...@@ -76,6 +76,11 @@ class Command(BaseCommand): ...@@ -76,6 +76,11 @@ class Command(BaseCommand):
for hist_module in hist_modules: for hist_module in hist_modules:
self.remove_studentmodulehistory_input_state(hist_module, save_changes) self.remove_studentmodulehistory_input_state(hist_module, save_changes)
if self.num_visited % 1000 == 0:
LOG.info(" Progress: updated {0} of {1} student modules".format(self.num_changed, self.num_visited))
LOG.info(" Progress: updated {0} of {1} student history modules".format(self.num_hist_changed,
self.num_hist_visited))
@transaction.autocommit @transaction.autocommit
def remove_studentmodule_input_state(self, module, save_changes): def remove_studentmodule_input_state(self, module, save_changes):
''' Fix the grade assigned to a StudentModule''' ''' Fix the grade assigned to a StudentModule'''
......
...@@ -15,7 +15,6 @@ from scipy.optimize import curve_fit ...@@ -15,7 +15,6 @@ from scipy.optimize import curve_fit
from django.conf import settings from django.conf import settings
from django.db.models import Sum, Max from django.db.models import Sum, Max
from psychometrics.models import * from psychometrics.models import *
from xmodule.modulestore import Location
log = logging.getLogger("mitx.psychometrics") log = logging.getLogger("mitx.psychometrics")
...@@ -246,13 +245,16 @@ def generate_plots_for_problem(problem): ...@@ -246,13 +245,16 @@ def generate_plots_for_problem(problem):
yset['ydat'] = ydat yset['ydat'] = ydat
if len(ydat) > 3: # try to fit to logistic function if enough data points if len(ydat) > 3: # try to fit to logistic function if enough data points
cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts / 2.0]) try:
yset['fitparam'] = cfp cfp = curve_fit(func_2pl, xdat, ydat, [1.0, max_attempts / 2.0])
yset['fitpts'] = func_2pl(np.array(xdat), *cfp[0]) yset['fitparam'] = cfp
yset['fiterr'] = [yd - yf for (yd, yf) in zip(ydat, yset['fitpts'])] yset['fitpts'] = func_2pl(np.array(xdat), *cfp[0])
fitx = np.linspace(xdat[0], xdat[-1], 100) yset['fiterr'] = [yd - yf for (yd, yf) in zip(ydat, yset['fitpts'])]
yset['fitx'] = fitx fitx = np.linspace(xdat[0], xdat[-1], 100)
yset['fity'] = func_2pl(np.array(fitx), *cfp[0]) yset['fitx'] = fitx
yset['fity'] = func_2pl(np.array(fitx), *cfp[0])
except Exception as err:
log.debug('Error in psychoanalyze curve fitting: %s' % err)
dataset['grade_%d' % grade] = yset dataset['grade_%d' % grade] = yset
...@@ -289,7 +291,7 @@ def generate_plots_for_problem(problem): ...@@ -289,7 +291,7 @@ def generate_plots_for_problem(problem):
'info': '', 'info': '',
'data': jsdata, 'data': jsdata,
'cmd': '[%s], %s' % (','.join(jsplots), axisopts), 'cmd': '[%s], %s' % (','.join(jsplots), axisopts),
}) })
#log.debug('plots = %s' % plots) #log.debug('plots = %s' % plots)
return msg, plots return msg, plots
...@@ -302,12 +304,12 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key): ...@@ -302,12 +304,12 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key):
Construct and return a procedure which may be called to update Construct and return a procedure which may be called to update
the PsychometricsData instance for the given StudentModule instance. the PsychometricsData instance for the given StudentModule instance.
""" """
sm = studentmodule.objects.get_or_create( sm, status = StudentModule.objects.get_or_create(
course_id=course_id, course_id=course_id,
student=user, student=user,
module_state_key=module_state_key, module_state_key=module_state_key,
defaults={'state': '{}', 'module_type': 'problem'}, defaults={'state': '{}', 'module_type': 'problem'},
) )
try: try:
pmd = PsychometricData.objects.using(db).get(studentmodule=sm) pmd = PsychometricData.objects.using(db).get(studentmodule=sm)
...@@ -329,7 +331,11 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key): ...@@ -329,7 +331,11 @@ def make_psychometrics_data_update_handler(course_id, user, module_state_key):
return return
pmd.done = done pmd.done = done
pmd.attempts = state['attempts'] try:
pmd.attempts = state.get('attempts', 0)
except:
log.exception("no attempts for %s (state=%s)" % (sm, sm.state))
try: try:
checktimes = eval(pmd.checktimes) # update log of attempt timestamps checktimes = eval(pmd.checktimes) # update log of attempt timestamps
except: except:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment