Commit dfa43501 by Adam Palay

notify students for NotFoundErrors from capa_module, improve error logging

parent d3993653
...@@ -152,6 +152,7 @@ class CapaFields(object): ...@@ -152,6 +152,7 @@ class CapaFields(object):
scope=Scope.settings scope=Scope.settings
) )
class CapaModule(CapaFields, XModule): class CapaModule(CapaFields, XModule):
""" """
An XModule implementing LonCapa format problems, implemented by way of An XModule implementing LonCapa format problems, implemented by way of
...@@ -552,6 +553,16 @@ class CapaModule(CapaFields, XModule): ...@@ -552,6 +553,16 @@ class CapaModule(CapaFields, XModule):
'ungraded_response': self.handle_ungraded_response 'ungraded_response': self.handle_ungraded_response
} }
generic_error_message = (
"We're sorry, there was an error with processing your request. "
"Please try reloading your page and trying again."
)
not_found_error_message = (
"The state of this problem has changed since you loaded this page. "
"Please refresh your page."
)
if dispatch not in handlers: if dispatch not in handlers:
return 'Error' return 'Error'
...@@ -559,9 +570,14 @@ class CapaModule(CapaFields, XModule): ...@@ -559,9 +570,14 @@ class CapaModule(CapaFields, XModule):
try: try:
result = handlers[dispatch](data) result = handlers[dispatch](data)
except NotFoundError as err:
_, _, traceback_obj = sys.exc_info()
raise ProcessingError, (not_found_error_message, err), traceback_obj
except Exception as err: except Exception as err:
_, _, traceback_obj = sys.exc_info() _, _, traceback_obj = sys.exc_info()
raise ProcessingError(err.message, traceback_obj) raise ProcessingError, (generic_error_message, err), traceback_obj
after = self.get_progress() after = self.get_progress()
...@@ -1125,7 +1141,6 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -1125,7 +1141,6 @@ class CapaDescriptor(CapaFields, RawDescriptor):
problem.rerandomize = "always" problem.rerandomize = "always"
return problem return problem
@property @property
def non_editable_metadata_fields(self): def non_editable_metadata_fields(self):
non_editable_fields = super(CapaDescriptor, self).non_editable_metadata_fields non_editable_fields = super(CapaDescriptor, self).non_editable_metadata_fields
......
...@@ -10,7 +10,7 @@ from django.core.cache import cache ...@@ -10,7 +10,7 @@ from django.core.cache import cache
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, HttpResponseBadRequest from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
import pyparsing import pyparsing
...@@ -38,6 +38,7 @@ from courseware.masquerade import setup_masquerade ...@@ -38,6 +38,7 @@ from courseware.masquerade import setup_masquerade
from courseware.model_data import LmsKeyValueStore, LmsUsage, ModelDataCache from courseware.model_data import LmsKeyValueStore, LmsUsage, ModelDataCache
from courseware.models import StudentModule from courseware.models import StudentModule
from util.sandboxing import can_execute_unsafe_code from util.sandboxing import can_execute_unsafe_code
from util.json_request import JsonResponse
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -509,11 +510,11 @@ def modx_dispatch(request, dispatch, location, course_id): ...@@ -509,11 +510,11 @@ def modx_dispatch(request, dispatch, location, course_id):
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 # For XModule-specific errors, we log the error and respond with an error message
except ProcessingError: except ProcessingError as err:
log.warning("Module encountered an error while prcessing AJAX call", log.warning("Module encountered an error while processing AJAX call",
exc_info=True) exc_info=True)
return HttpResponseBadRequest() return JsonResponse(object={'success': err.args[0]}, status=200)
# If any other error occurred, re-raise it to trigger a 500 response # If any other error occurred, re-raise it to trigger a 500 response
except: except:
......
...@@ -107,6 +107,15 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -107,6 +107,15 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
resp = self.client.post(modx_url) resp = self.client.post(modx_url)
return resp return resp
def show_question_answer(self, problem_url_name):
"""
Shows the answer to the current student.
"""
problem_location = self.problem_location(problem_url_name)
modx_url = self.modx_url(problem_location, 'problem_show')
resp = self.client.post(modx_url)
return resp
def add_dropdown_to_section(self, section_location, name, num_inputs=2): def add_dropdown_to_section(self, section_location, name, num_inputs=2):
""" """
Create and return a dropdown problem. Create and return a dropdown problem.
...@@ -131,7 +140,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -131,7 +140,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
parent_location=section_location, parent_location=section_location,
category='problem', category='problem',
data=prob_xml, data=prob_xml,
metadata={'randomize': 'always'}, metadata={'rerandomize': 'always'},
display_name=name display_name=name
) )
...@@ -139,7 +148,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -139,7 +148,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.refresh_course() self.refresh_course()
return problem return problem
def add_graded_section_to_course(self, name, section_format='Homework'): def add_graded_section_to_course(self, name, section_format='Homework', late=False, reset=False, showanswer=False):
""" """
Creates a graded homework section within a chapter and returns the section. Creates a graded homework section within a chapter and returns the section.
""" """
...@@ -151,12 +160,44 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -151,12 +160,44 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase):
category='chapter' category='chapter'
) )
section = ItemFactory.create( if late:
parent_location=self.chapter.location, section = ItemFactory.create(
display_name=name, parent_location=self.chapter.location,
category='sequential', display_name=name,
metadata={'graded': True, 'format': section_format} category='sequential',
) metadata={'graded': True, 'format': section_format, 'due': '2013-05-20T23:30'}
)
elif reset:
section = ItemFactory.create(
parent_location=self.chapter.location,
display_name=name,
category='sequential',
rerandomize='always',
metadata={
'graded': True,
'format': section_format,
}
)
elif showanswer:
section = ItemFactory.create(
parent_location=self.chapter.location,
display_name=name,
category='sequential',
showanswer='never',
metadata={
'graded': True,
'format': section_format,
}
)
else:
section = ItemFactory.create(
parent_location=self.chapter.location,
display_name=name,
category='sequential',
metadata={'graded': True, 'format': section_format}
)
# now that we've added the problem and section to the course # now that we've added the problem and section to the course
# we fetch the course from the database so the object we are # we fetch the course from the database so the object we are
...@@ -257,7 +298,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -257,7 +298,7 @@ class TestCourseGrader(TestSubmittingProblems):
hw_section = next(section for section in sections_list if section.get('url_name') == hw_url_name) hw_section = next(section for section in sections_list if section.get('url_name') == hw_url_name)
return [s.earned for s in hw_section['scores']] return [s.earned for s in hw_section['scores']]
def basic_setup(self): def basic_setup(self, late=False, reset=False, showanswer=False):
""" """
Set up a simple course for testing basic grading functionality. Set up a simple course for testing basic grading functionality.
""" """
...@@ -278,7 +319,7 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -278,7 +319,7 @@ class TestCourseGrader(TestSubmittingProblems):
self.add_grading_policy(grading_policy) self.add_grading_policy(grading_policy)
# set up a simple course with four problems # set up a simple course with four problems
self.homework = self.add_graded_section_to_course('homework') self.homework = self.add_graded_section_to_course('homework', late=late, reset=reset, showanswer=showanswer)
self.add_dropdown_to_section(self.homework.location, 'p1', 1) self.add_dropdown_to_section(self.homework.location, 'p1', 1)
self.add_dropdown_to_section(self.homework.location, 'p2', 1) self.add_dropdown_to_section(self.homework.location, 'p2', 1)
self.add_dropdown_to_section(self.homework.location, 'p3', 1) self.add_dropdown_to_section(self.homework.location, 'p3', 1)
...@@ -346,6 +387,41 @@ class TestCourseGrader(TestSubmittingProblems): ...@@ -346,6 +387,41 @@ class TestCourseGrader(TestSubmittingProblems):
self.add_dropdown_to_section(self.homework3.location, self.hw3_names[0], 1) self.add_dropdown_to_section(self.homework3.location, self.hw3_names[0], 1)
self.add_dropdown_to_section(self.homework3.location, self.hw3_names[1], 1) self.add_dropdown_to_section(self.homework3.location, self.hw3_names[1], 1)
def test_submission_late(self):
"""Test problem for due date in the past"""
self.basic_setup(late=True)
resp = self.submit_question_answer('p1', {'2_1': 'Correct'})
self.assertEqual(resp.status_code, 200)
err_msg = (
"The state of this problem has changed since you loaded this page. "
"Please refresh your page."
)
self.assertEqual(json.loads(resp.content).get("success"), err_msg)
def test_submission_reset(self):
"""Test problem ProcessingErrors due to resets"""
self.basic_setup(reset=True)
resp = self.submit_question_answer('p1', {'2_1': 'Correct'})
# submit a second time to draw NotFoundError
resp = self.submit_question_answer('p1', {'2_1': 'Correct'})
self.assertEqual(resp.status_code, 200)
err_msg = (
"The state of this problem has changed since you loaded this page. "
"Please refresh your page."
)
self.assertEqual(json.loads(resp.content).get("success"), err_msg)
def test_submission_show_answer(self):
"""Test problem for ProcessingErrors due to showing answer"""
self.basic_setup(showanswer=True)
resp = self.show_question_answer('p1')
self.assertEqual(resp.status_code, 200)
err_msg = (
"The state of this problem has changed since you loaded this page. "
"Please refresh your page."
)
self.assertEqual(json.loads(resp.content).get("success"), err_msg)
def test_none_grade(self): def test_none_grade(self):
""" """
Check grade is 0 to begin with. Check grade is 0 to begin with.
......
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