Commit 9c4a88b5 by Victor Shnayder

Merge pull request #1743 from MITx/fix/will/bug_288

Fix/will/bug 288
parents fd0dc92c 9c671163
...@@ -42,7 +42,7 @@ from xmodule.modulestore.mongo import MongoUsage ...@@ -42,7 +42,7 @@ from xmodule.modulestore.mongo import MongoUsage
from mitxmako.shortcuts import render_to_response, render_to_string from mitxmako.shortcuts import render_to_response, render_to_string
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule_modifiers import replace_static_urls, wrap_xmodule from xmodule_modifiers import replace_static_urls, wrap_xmodule
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError, ProcessingError
from functools import partial from functools import partial
from xmodule.contentstore.django import contentstore from xmodule.contentstore.django import contentstore
...@@ -439,9 +439,16 @@ def preview_dispatch(request, preview_id, location, dispatch=None): ...@@ -439,9 +439,16 @@ def preview_dispatch(request, preview_id, location, dispatch=None):
# Let the module handle the AJAX # Let the module handle the AJAX
try: try:
ajax_return = instance.handle_ajax(dispatch, request.POST) ajax_return = instance.handle_ajax(dispatch, request.POST)
except NotFoundError: except NotFoundError:
log.exception("Module indicating to user that request doesn't exist") log.exception("Module indicating to user that request doesn't exist")
raise Http404 raise Http404
except ProcessingError:
log.warning("Module raised an error while processing AJAX request",
exc_info=True)
return HttpResponseBadRequest()
except: except:
log.exception("error processing ajax call") log.exception("error processing ajax call")
raise raise
......
...@@ -17,6 +17,7 @@ import logging ...@@ -17,6 +17,7 @@ import logging
import numbers import numbers
import numpy import numpy
import os import os
import sys
import random import random
import re import re
import requests import requests
...@@ -52,12 +53,17 @@ class LoncapaProblemError(Exception): ...@@ -52,12 +53,17 @@ class LoncapaProblemError(Exception):
class ResponseError(Exception): class ResponseError(Exception):
''' '''
Error for failure in processing a response Error for failure in processing a response, including
exceptions that occur when executing a custom script.
''' '''
pass pass
class StudentInputError(Exception): class StudentInputError(Exception):
'''
Error for an invalid student input.
For example, submitting a string when the problem expects a number
'''
pass pass
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
...@@ -833,7 +839,7 @@ class NumericalResponse(LoncapaResponse): ...@@ -833,7 +839,7 @@ class NumericalResponse(LoncapaResponse):
import sys import sys
type, value, traceback = sys.exc_info() type, value, traceback = sys.exc_info()
raise StudentInputError, ("Invalid input: could not interpret '%s' as a number" % raise StudentInputError, ("Could not interpret '%s' as a number" %
cgi.escape(student_answer)), traceback cgi.escape(student_answer)), traceback
if correct: if correct:
...@@ -1072,13 +1078,10 @@ def sympy_check2(): ...@@ -1072,13 +1078,10 @@ def sympy_check2():
correct = self.context['correct'] correct = self.context['correct']
messages = self.context['messages'] messages = self.context['messages']
overall_message = self.context['overall_message'] overall_message = self.context['overall_message']
except Exception as err: except Exception as err:
print "oops in customresponse (code) error %s" % err self._handle_exec_exception(err)
print "context = ", self.context
print traceback.format_exc()
# Notify student
raise StudentInputError(
"Error: Problem could not be evaluated with your input")
else: else:
# self.code is not a string; assume its a function # self.code is not a string; assume its a function
...@@ -1105,13 +1108,9 @@ def sympy_check2(): ...@@ -1105,13 +1108,9 @@ def sympy_check2():
nargs, args, kwargs)) nargs, args, kwargs))
ret = fn(*args[:nargs], **kwargs) ret = fn(*args[:nargs], **kwargs)
except Exception as err: except Exception as err:
log.error("oops in customresponse (cfn) error %s" % err) self._handle_exec_exception(err)
# print "context = ",self.context
log.error(traceback.format_exc())
raise Exception("oops in customresponse (cfn) error %s" % err)
log.debug(
"[courseware.capa.responsetypes.customresponse.get_score] ret = %s" % ret)
if type(ret) == dict: if type(ret) == dict:
...@@ -1147,9 +1146,9 @@ def sympy_check2(): ...@@ -1147,9 +1146,9 @@ def sympy_check2():
correct = [] correct = []
messages = [] messages = []
for input_dict in input_list: for input_dict in input_list:
correct.append('correct' correct.append('correct'
if input_dict['ok'] else 'incorrect') if input_dict['ok'] else 'incorrect')
msg = (self.clean_message_html(input_dict['msg']) msg = (self.clean_message_html(input_dict['msg'])
if 'msg' in input_dict else None) if 'msg' in input_dict else None)
messages.append(msg) messages.append(msg)
...@@ -1157,7 +1156,7 @@ def sympy_check2(): ...@@ -1157,7 +1156,7 @@ def sympy_check2():
# Raise an exception # Raise an exception
else: else:
log.error(traceback.format_exc()) log.error(traceback.format_exc())
raise Exception( raise ResponseError(
"CustomResponse: check function returned an invalid dict") "CustomResponse: check function returned an invalid dict")
# The check function can return a boolean value, # The check function can return a boolean value,
...@@ -1174,7 +1173,7 @@ def sympy_check2(): ...@@ -1174,7 +1173,7 @@ def sympy_check2():
correct_map.set_overall_message(overall_message) correct_map.set_overall_message(overall_message)
for k in range(len(idset)): for k in range(len(idset)):
npoints = (self.maxpoints[idset[k]] npoints = (self.maxpoints[idset[k]]
if correct[k] == 'correct' else 0) if correct[k] == 'correct' else 0)
correct_map.set(idset[k], correct[k], msg=messages[k], correct_map.set(idset[k], correct[k], msg=messages[k],
npoints=npoints) npoints=npoints)
...@@ -1227,6 +1226,22 @@ def sympy_check2(): ...@@ -1227,6 +1226,22 @@ def sympy_check2():
return {self.answer_ids[0]: self.expect} return {self.answer_ids[0]: self.expect}
return self.default_answer_map return self.default_answer_map
def _handle_exec_exception(self, err):
'''
Handle an exception raised during the execution of
custom Python code.
Raises a ResponseError
'''
# Log the error if we are debugging
msg = 'Error occurred while evaluating CustomResponse'
log.warning(msg, exc_info=True)
# Notify student with a student input error
_, _, traceback_obj = sys.exc_info()
raise ResponseError, err.message, traceback_obj
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
...@@ -1901,7 +1916,14 @@ class SchematicResponse(LoncapaResponse): ...@@ -1901,7 +1916,14 @@ class SchematicResponse(LoncapaResponse):
submission = [json.loads(student_answers[ submission = [json.loads(student_answers[
k]) for k in sorted(self.answer_ids)] k]) for k in sorted(self.answer_ids)]
self.context.update({'submission': submission}) self.context.update({'submission': submission})
exec self.code in global_context, self.context
try:
exec self.code in global_context, self.context
except Exception as err:
_, _, traceback_obj = sys.exc_info()
raise ResponseError, ResponseError(err.message), traceback_obj
cmap = CorrectMap() cmap = CorrectMap()
cmap.set_dict(dict(zip(sorted( cmap.set_dict(dict(zip(sorted(
self.answer_ids), self.context['correct']))) self.answer_ids), self.context['correct'])))
...@@ -2106,7 +2128,7 @@ class AnnotationResponse(LoncapaResponse): ...@@ -2106,7 +2128,7 @@ class AnnotationResponse(LoncapaResponse):
option_scoring = dict([(option['id'], { option_scoring = dict([(option['id'], {
'correctness': choices.get(option['choice']), 'correctness': choices.get(option['choice']),
'points': scoring.get(option['choice']) 'points': scoring.get(option['choice'])
}) for option in self._find_options(inputfield) ]) }) for option in self._find_options(inputfield)])
scoring_map[inputfield.get('id')] = option_scoring scoring_map[inputfield.get('id')] = option_scoring
......
...@@ -13,6 +13,8 @@ import textwrap ...@@ -13,6 +13,8 @@ import textwrap
from . import test_system from . import test_system
import capa.capa_problem as lcp import capa.capa_problem as lcp
from capa.responsetypes import LoncapaProblemError, \
StudentInputError, ResponseError
from capa.correctmap import CorrectMap from capa.correctmap import CorrectMap
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from capa.xqueue_interface import dateformat from capa.xqueue_interface import dateformat
...@@ -864,7 +866,7 @@ class CustomResponseTest(ResponseTest): ...@@ -864,7 +866,7 @@ class CustomResponseTest(ResponseTest):
# Message is interpreted as an "overall message" # Message is interpreted as an "overall message"
self.assertEqual(correct_map.get_overall_message(), 'Message text') self.assertEqual(correct_map.get_overall_message(), 'Message text')
def test_script_exception(self): def test_script_exception_function(self):
# Construct a script that will raise an exception # Construct a script that will raise an exception
script = textwrap.dedent(""" script = textwrap.dedent("""
...@@ -875,7 +877,17 @@ class CustomResponseTest(ResponseTest): ...@@ -875,7 +877,17 @@ class CustomResponseTest(ResponseTest):
problem = self.build_problem(script=script, cfn="check_func") problem = self.build_problem(script=script, cfn="check_func")
# Expect that an exception gets raised when we check the answer # Expect that an exception gets raised when we check the answer
with self.assertRaises(Exception): with self.assertRaises(ResponseError):
problem.grade_answers({'1_2_1': '42'})
def test_script_exception_inline(self):
# Construct a script that will raise an exception
script = 'raise Exception("Test")'
problem = self.build_problem(answer=script)
# Expect that an exception gets raised when we check the answer
with self.assertRaises(ResponseError):
problem.grade_answers({'1_2_1': '42'}) problem.grade_answers({'1_2_1': '42'})
def test_invalid_dict_exception(self): def test_invalid_dict_exception(self):
...@@ -889,7 +901,7 @@ class CustomResponseTest(ResponseTest): ...@@ -889,7 +901,7 @@ class CustomResponseTest(ResponseTest):
problem = self.build_problem(script=script, cfn="check_func") problem = self.build_problem(script=script, cfn="check_func")
# Expect that an exception gets raised when we check the answer # Expect that an exception gets raised when we check the answer
with self.assertRaises(Exception): with self.assertRaises(ResponseError):
problem.grade_answers({'1_2_1': '42'}) problem.grade_answers({'1_2_1': '42'})
...@@ -922,6 +934,18 @@ class SchematicResponseTest(ResponseTest): ...@@ -922,6 +934,18 @@ class SchematicResponseTest(ResponseTest):
# is what we expect) # is what we expect)
self.assertEqual(correct_map.get_correctness('1_2_1'), 'correct') self.assertEqual(correct_map.get_correctness('1_2_1'), 'correct')
def test_script_exception(self):
# Construct a script that will raise an exception
script = "raise Exception('test')"
problem = self.build_problem(answer=script)
# Expect that an exception gets raised when we check the answer
with self.assertRaises(ResponseError):
submission_dict = {'test': 'test'}
input_dict = {'1_2_1': json.dumps(submission_dict)}
problem.grade_answers(input_dict)
class AnnotationResponseTest(ResponseTest): class AnnotationResponseTest(ResponseTest):
from response_xml_factory import AnnotationResponseXMLFactory from response_xml_factory import AnnotationResponseXMLFactory
......
...@@ -12,12 +12,13 @@ from lxml import etree ...@@ -12,12 +12,13 @@ from lxml import etree
from pkg_resources import resource_string from pkg_resources import resource_string
from capa.capa_problem import LoncapaProblem from capa.capa_problem import LoncapaProblem
from capa.responsetypes import StudentInputError from capa.responsetypes import StudentInputError, \
ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames from capa.util import convert_files_to_filenames
from .progress import Progress from .progress import Progress
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float
from .fields import Timedelta from .fields import Timedelta
...@@ -454,7 +455,14 @@ class CapaModule(CapaFields, XModule): ...@@ -454,7 +455,14 @@ class CapaModule(CapaFields, XModule):
return 'Error' return 'Error'
before = self.get_progress() before = self.get_progress()
d = handlers[dispatch](get)
try:
d = handlers[dispatch](get)
except Exception as err:
_, _, traceback_obj = sys.exc_info()
raise ProcessingError, err.message, traceback_obj
after = self.get_progress() after = self.get_progress()
d.update({ d.update({
'progress_changed': after != before, 'progress_changed': after != before,
...@@ -576,7 +584,7 @@ class CapaModule(CapaFields, XModule): ...@@ -576,7 +584,7 @@ class CapaModule(CapaFields, XModule):
# save any state changes that may occur # save any state changes that may occur
self.set_state_from_lcp() self.set_state_from_lcp()
return response return response
def get_answer(self, get): def get_answer(self, get):
''' '''
...@@ -725,9 +733,24 @@ class CapaModule(CapaFields, XModule): ...@@ -725,9 +733,24 @@ class CapaModule(CapaFields, XModule):
try: try:
correct_map = self.lcp.grade_answers(answers) correct_map = self.lcp.grade_answers(answers)
self.set_state_from_lcp() self.set_state_from_lcp()
except StudentInputError as inst:
log.exception("StudentInputError in capa_module:problem_check") except (StudentInputError, ResponseError, LoncapaProblemError) as inst:
return {'success': inst.message} log.warning("StudentInputError in capa_module:problem_check",
exc_info=True)
# If the user is a staff member, include
# the full exception, including traceback,
# in the response
if self.system.user_is_staff:
msg = "Staff debug info: %s" % traceback.format_exc()
# Otherwise, display just an error message,
# without a stack trace
else:
msg = "Error: %s" % str(inst.message)
return {'success': msg}
except Exception, err: except Exception, err:
if self.system.DEBUG: if self.system.DEBUG:
msg = "Error checking problem: " + str(err) msg = "Error checking problem: " + str(err)
...@@ -778,7 +801,7 @@ class CapaModule(CapaFields, XModule): ...@@ -778,7 +801,7 @@ class CapaModule(CapaFields, XModule):
event_info['answers'] = answers event_info['answers'] = answers
# Too late. Cannot submit # Too late. Cannot submit
if self.closed() and not self.max_attempts ==0: if self.closed() and not self.max_attempts == 0:
event_info['failure'] = 'closed' event_info['failure'] = 'closed'
self.system.track_function('save_problem_fail', event_info) self.system.track_function('save_problem_fail', event_info)
return {'success': False, return {'success': False,
...@@ -798,7 +821,7 @@ class CapaModule(CapaFields, XModule): ...@@ -798,7 +821,7 @@ class CapaModule(CapaFields, XModule):
self.system.track_function('save_problem_success', event_info) self.system.track_function('save_problem_success', event_info)
msg = "Your answers have been saved" msg = "Your answers have been saved"
if not self.max_attempts ==0: if not self.max_attempts == 0:
msg += " but not graded. Hit 'Check' to grade them." msg += " but not graded. Hit 'Check' to grade them."
return {'success': True, return {'success': True,
'msg': msg} 'msg': msg}
......
class InvalidDefinitionError(Exception): class InvalidDefinitionError(Exception):
pass pass
class NotFoundError(Exception): class NotFoundError(Exception):
pass pass
class ProcessingError(Exception):
'''
An error occurred while processing a request to the XModule.
For example: if an exception occurs while checking a capa problem.
'''
pass
...@@ -7,6 +7,8 @@ import random ...@@ -7,6 +7,8 @@ import random
import xmodule import xmodule
import capa import capa
from capa.responsetypes import StudentInputError, \
LoncapaProblemError, ResponseError
from xmodule.capa_module import CapaModule from xmodule.capa_module import CapaModule
from xmodule.modulestore import Location from xmodule.modulestore import Location
from lxml import etree from lxml import etree
...@@ -407,7 +409,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -407,7 +409,7 @@ class CapaModuleTest(unittest.TestCase):
mock_html.return_value = "Test HTML" mock_html.return_value = "Test HTML"
# Check the problem # Check the problem
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict) result = module.check_problem(get_request_dict)
# Expect that the problem is marked correct # Expect that the problem is marked correct
...@@ -428,7 +430,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -428,7 +430,7 @@ class CapaModuleTest(unittest.TestCase):
mock_is_correct.return_value = False mock_is_correct.return_value = False
# Check the problem # Check the problem
get_request_dict = { CapaFactory.input_key(): '0'} get_request_dict = {CapaFactory.input_key(): '0'}
result = module.check_problem(get_request_dict) result = module.check_problem(get_request_dict)
# Expect that the problem is marked correct # Expect that the problem is marked correct
...@@ -446,7 +448,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -446,7 +448,7 @@ class CapaModuleTest(unittest.TestCase):
with patch('xmodule.capa_module.CapaModule.closed') as mock_closed: with patch('xmodule.capa_module.CapaModule.closed') as mock_closed:
mock_closed.return_value = True mock_closed.return_value = True
with self.assertRaises(xmodule.exceptions.NotFoundError): with self.assertRaises(xmodule.exceptions.NotFoundError):
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 number of attempts NOT incremented # Expect that number of attempts NOT incremented
...@@ -492,7 +494,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -492,7 +494,7 @@ class CapaModuleTest(unittest.TestCase):
mock_is_queued.return_value = True mock_is_queued.return_value = True
mock_get_queuetime.return_value = datetime.datetime.now() mock_get_queuetime.return_value = datetime.datetime.now()
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict) result = module.check_problem(get_request_dict)
# Expect an AJAX alert message in 'success' # Expect an AJAX alert message in 'success'
...@@ -502,21 +504,61 @@ class CapaModuleTest(unittest.TestCase): ...@@ -502,21 +504,61 @@ class CapaModuleTest(unittest.TestCase):
self.assertEqual(module.attempts, 1) self.assertEqual(module.attempts, 1)
def test_check_problem_student_input_error(self): def test_check_problem_error(self):
module = CapaFactory.create(attempts=1)
# Simulate a student input exception # Try each exception that capa_module should handle
with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade: for exception_class in [StudentInputError,
mock_grade.side_effect = capa.responsetypes.StudentInputError('test error') LoncapaProblemError,
ResponseError]:
get_request_dict = { CapaFactory.input_key(): '3.14'} # Create the module
result = module.check_problem(get_request_dict) module = CapaFactory.create(attempts=1)
# Ensure that the user is NOT staff
module.system.user_is_staff = False
# Simulate answering a problem that raises the exception
with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade:
mock_grade.side_effect = exception_class('test error')
get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict)
# Expect an AJAX alert message in 'success'
expected_msg = 'Error: test error'
self.assertEqual(expected_msg, result['success'])
# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
def test_check_problem_error_with_staff_user(self):
# Try each exception that capa module should handle
for exception_class in [StudentInputError,
LoncapaProblemError,
ResponseError]:
# Create the module
module = CapaFactory.create(attempts=1)
# Ensure that the user IS staff
module.system.user_is_staff = True
# Simulate answering a problem that raises an exception
with patch('capa.capa_problem.LoncapaProblem.grade_answers') as mock_grade:
mock_grade.side_effect = exception_class('test error')
get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.check_problem(get_request_dict)
# Expect an AJAX alert message in 'success' # Expect an AJAX alert message in 'success'
self.assertTrue('test error' in result['success']) self.assertTrue('test error' in result['success'])
# Expect that the number of attempts is NOT incremented # We DO include traceback information for staff users
self.assertEqual(module.attempts, 1) self.assertTrue('Traceback' in result['success'])
# Expect that the number of attempts is NOT incremented
self.assertEqual(module.attempts, 1)
def test_reset_problem(self): def test_reset_problem(self):
...@@ -573,11 +615,11 @@ class CapaModuleTest(unittest.TestCase): ...@@ -573,11 +615,11 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(done=False) module = CapaFactory.create(done=False)
# Save the problem # Save the problem
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that answers are saved to the problem # Expect that answers are saved to the problem
expected_answers = { CapaFactory.answer_key(): '3.14'} expected_answers = {CapaFactory.answer_key(): '3.14'}
self.assertEqual(module.lcp.student_answers, expected_answers) self.assertEqual(module.lcp.student_answers, expected_answers)
# Expect that the result is success # Expect that the result is success
...@@ -592,7 +634,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -592,7 +634,7 @@ class CapaModuleTest(unittest.TestCase):
mock_closed.return_value = True mock_closed.return_value = True
# Try to save the problem # Try to save the problem
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that the result is failure # Expect that the result is failure
...@@ -603,7 +645,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -603,7 +645,7 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(rerandomize='always', done=True) module = CapaFactory.create(rerandomize='always', done=True)
# Try to save # Try to save
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that we cannot save # Expect that we cannot save
...@@ -614,7 +656,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -614,7 +656,7 @@ class CapaModuleTest(unittest.TestCase):
module = CapaFactory.create(rerandomize='never', done=True) module = CapaFactory.create(rerandomize='never', done=True)
# Try to save # Try to save
get_request_dict = { CapaFactory.input_key(): '3.14'} get_request_dict = {CapaFactory.input_key(): '3.14'}
result = module.save_problem(get_request_dict) result = module.save_problem(get_request_dict)
# Expect that we succeed # Expect that we succeed
...@@ -626,7 +668,7 @@ class CapaModuleTest(unittest.TestCase): ...@@ -626,7 +668,7 @@ class CapaModuleTest(unittest.TestCase):
# Just in case, we also check what happens if we have # Just in case, we also check what happens if we have
# more attempts than allowed. # more attempts than allowed.
attempts = random.randint(1, 10) attempts = random.randint(1, 10)
module = CapaFactory.create(attempts=attempts -1, max_attempts=attempts) module = CapaFactory.create(attempts=attempts - 1, max_attempts=attempts)
self.assertEqual(module.check_button_name(), "Final Check") self.assertEqual(module.check_button_name(), "Final Check")
module = CapaFactory.create(attempts=attempts, max_attempts=attempts) module = CapaFactory.create(attempts=attempts, max_attempts=attempts)
...@@ -636,14 +678,14 @@ class CapaModuleTest(unittest.TestCase): ...@@ -636,14 +678,14 @@ class CapaModuleTest(unittest.TestCase):
self.assertEqual(module.check_button_name(), "Final Check") self.assertEqual(module.check_button_name(), "Final Check")
# Otherwise, button name is "Check" # Otherwise, button name is "Check"
module = CapaFactory.create(attempts=attempts -2, max_attempts=attempts) module = CapaFactory.create(attempts=attempts - 2, max_attempts=attempts)
self.assertEqual(module.check_button_name(), "Check") self.assertEqual(module.check_button_name(), "Check")
module = CapaFactory.create(attempts=attempts -3, max_attempts=attempts) module = CapaFactory.create(attempts=attempts - 3, max_attempts=attempts)
self.assertEqual(module.check_button_name(), "Check") self.assertEqual(module.check_button_name(), "Check")
# If no limit on attempts, then always show "Check" # If no limit on attempts, then always show "Check"
module = CapaFactory.create(attempts=attempts -3) module = CapaFactory.create(attempts=attempts - 3)
self.assertEqual(module.check_button_name(), "Check") self.assertEqual(module.check_button_name(), "Check")
module = CapaFactory.create(attempts=0) module = CapaFactory.create(attempts=0)
......
...@@ -11,7 +11,7 @@ from django.contrib.auth.models import User ...@@ -11,7 +11,7 @@ from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import Http404 from django.http import Http404
from django.http import HttpResponse from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
...@@ -23,7 +23,7 @@ from .models import StudentModule ...@@ -23,7 +23,7 @@ from .models import StudentModule
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from student.models import unique_id_for_user from student.models import unique_id_for_user
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
...@@ -443,9 +443,19 @@ def modx_dispatch(request, dispatch, location, course_id): ...@@ -443,9 +443,19 @@ def modx_dispatch(request, dispatch, location, course_id):
# Let the module handle the AJAX # Let the module handle the AJAX
try: try:
ajax_return = instance.handle_ajax(dispatch, p) ajax_return = instance.handle_ajax(dispatch, p)
# If we can't find the module, respond with a 404
except NotFoundError: except NotFoundError:
log.exception("Module indicating to user that request doesn't exist") log.exception("Module indicating to user that request doesn't exist")
raise Http404 raise Http404
# For XModule-specific errors, we respond with 400
except ProcessingError:
log.warning("Module encountered an error while prcessing AJAX call",
exc_info=True)
return HttpResponseBadRequest()
# If any other error occurred, re-raise it to trigger a 500 response
except: except:
log.exception("error processing ajax call") log.exception("error processing ajax call")
raise raise
......
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