diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index ade5cc6ef6..9ae6583c50 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -136,7 +136,7 @@ class CapaModule(XModule):
self.close_date = self.display_due_date
max_attempts = self.metadata.get('attempts', None)
- if max_attempts:
+ if max_attempts is not None:
self.max_attempts = int(max_attempts)
else:
self.max_attempts = None
@@ -247,123 +247,176 @@ class CapaModule(XModule):
'progress': Progress.to_js_status_str(self.get_progress())
})
+ def check_button_name(self):
+ """
+ Determine the name for the "check" button.
+ Usually it is just "Check", but if this is the student's
+ final attempt, change the name to "Final Check"
+ """
+ if self.max_attempts is not None:
+ final_check = (self.attempts >= self.max_attempts - 1)
+ else:
+ final_check = False
+
+ return "Final Check" if final_check else "Check"
+
+ def should_show_check_button(self):
+ """
+ Return True/False to indicate whether to show the "Check" button.
+ """
+ submitted_without_reset = (self.is_completed() and self.rerandomize == "always")
+
+ # If the problem is closed (past due / too many attempts)
+ # then we do NOT show the "check" button
+ # Also, do not show the "check" button if we're waiting
+ # for the user to reset a randomized problem
+ if self.closed() or submitted_without_reset:
+ return False
+ else:
+ return True
+
+ def should_show_reset_button(self):
+ """
+ Return True/False to indicate whether to show the "Reset" button.
+ """
+ is_survey_question = (self.max_attempts == 0)
+
+ if self.rerandomize in ["always", "onreset"]:
+
+ # If the problem is closed (and not a survey question with max_attempts==0),
+ # then do NOT show the reset button.
+ # If the problem hasn't been submitted yet, then do NOT show
+ # the reset button.
+ if (self.closed() and not is_survey_question) or not self.is_completed():
+ return False
+ else:
+ return True
+
+ # Only randomized problems need a "reset" button
+ else:
+ return False
+
+ def should_show_save_button(self):
+ """
+ Return True/False to indicate whether to show the "Save" button.
+ """
+
+ # If the user has forced the save button to display,
+ # then show it as long as the problem is not closed
+ # (past due / too many attempts)
+ if self.force_save_button == "true":
+ return not self.closed()
+ else:
+ is_survey_question = (self.max_attempts == 0)
+ needs_reset = self.is_completed() and self.rerandomize == "always"
+
+ # If the problem is closed (and not a survey question with max_attempts==0),
+ # then do NOT show the reset button
+ # If we're waiting for the user to reset a randomized problem
+ # then do NOT show the reset button
+ if (self.closed() and not is_survey_question) or needs_reset:
+ return False
+ else:
+ return True
+
+ def handle_problem_html_error(self, err):
+ """
+ Change our problem to a dummy problem containing
+ a warning message to display to users.
+
+ Returns the HTML to show to users
+
+ *err* is the Exception encountered while rendering the problem HTML.
+ """
+ log.exception(err)
+
+ # TODO (vshnayder): another switch on DEBUG.
+ if self.system.DEBUG:
+ msg = (
+ '[courseware.capa.capa_module] '
+ 'Failed to generate HTML for problem %s' %
+ (self.location.url()))
+ msg += '
+ html_encapsulated = module.get_problem_html(encapsulate=True)
+
+ # Expect that we get the rendered template back
+ self.assertEqual(html, "
Test Template HTML
")
+
+ # Check the rendering context
+ render_args,_ = module.system.render_template.call_args
+ self.assertEqual(len(render_args), 2)
+
+ template_name = render_args[0]
+ self.assertEqual(template_name, "problem.html")
+
+ context = render_args[1]
+ self.assertEqual(context['problem']['html'], "
Test Problem HTML
")
+ self.assertEqual(bool(context['check_button']), show_check_button)
+ self.assertEqual(bool(context['reset_button']), show_reset_button)
+ self.assertEqual(bool(context['save_button']), show_save_button)
+
+ # Assert that the encapsulated html contains the original html
+ self.assertTrue(html in html_encapsulated)
+
+
+ def test_get_problem_html_error(self):
+ """
+ In production, when an error occurs with the problem HTML
+ rendering, a "dummy" problem is created with an error
+ message to display to the user.
+ """
+ module = CapaFactory.create()
+
+ # Save the original problem so we can compare it later
+ original_problem = module.lcp
+
+ # Simulate throwing an exception when the capa problem
+ # is asked to render itself as HTML
+ module.lcp.get_html = Mock(side_effect=Exception("Test"))
+
+ # Stub out the test_system rendering function
+ module.system.render_template = Mock(return_value="
Test Template HTML
")
+
+ # Turn off DEBUG
+ module.system.DEBUG = False
+
+ # Try to render the module with DEBUG turned off
+ html = module.get_problem_html()
+
+ # Check the rendering context
+ render_args,_ = module.system.render_template.call_args
+ context = render_args[1]
+ self.assertTrue("error" in context['problem']['html'])
+
+ # Expect that the module has created a new dummy problem with the error
+ self.assertNotEqual(original_problem, module.lcp)
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 5f6496f823..a524ac2fd9 100644
--- a/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+++ b/common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
@@ -54,7 +54,8 @@ class OpenEndedChildTest(unittest.TestCase):
descriptor = Mock()
def setUp(self):
- self.openendedchild = OpenEndedChild(test_system, self.location,
+ self.test_system = test_system()
+ self.openendedchild = OpenEndedChild(self.test_system, self.location,
self.definition, self.descriptor, self.static_data, self.metadata)
@@ -69,7 +70,7 @@ class OpenEndedChildTest(unittest.TestCase):
def test_latest_post_assessment_empty(self):
- answer = self.openendedchild.latest_post_assessment(test_system)
+ answer = self.openendedchild.latest_post_assessment(self.test_system)
self.assertEqual(answer, "")
@@ -106,7 +107,7 @@ class OpenEndedChildTest(unittest.TestCase):
post_assessment = "Post assessment"
self.openendedchild.record_latest_post_assessment(post_assessment)
self.assertEqual(post_assessment,
- self.openendedchild.latest_post_assessment(test_system))
+ self.openendedchild.latest_post_assessment(self.test_system))
def test_get_score(self):
new_answer = "New Answer"
@@ -125,7 +126,7 @@ class OpenEndedChildTest(unittest.TestCase):
def test_reset(self):
- self.openendedchild.reset(test_system)
+ self.openendedchild.reset(self.test_system)
state = json.loads(self.openendedchild.get_instance_state())
self.assertEqual(state['state'], OpenEndedChild.INITIAL)
@@ -182,11 +183,13 @@ class OpenEndedModuleTest(unittest.TestCase):
descriptor = Mock()
def setUp(self):
- test_system.location = self.location
+ self.test_system = test_system()
+
+ 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.test_system.xqueue = {'interface': self.mock_xqueue, 'callback_url': '/', 'default_queuename': 'testqueue', 'waittime': 1}
+ self.openendedmodule = OpenEndedModule(self.test_system, self.location,
self.definition, self.descriptor, self.static_data, self.metadata)
def test_message_post(self):
@@ -195,7 +198,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'grader_id': '1',
'score': 3}
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
- student_info = {'anonymous_student_id': test_system.anonymous_student_id,
+ student_info = {'anonymous_student_id': self.test_system.anonymous_student_id,
'submission_time': qtime}
contents = {
'feedback': get['feedback'],
@@ -205,7 +208,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'student_info': json.dumps(student_info)
}
- result = self.openendedmodule.message_post(get, test_system)
+ result = self.openendedmodule.message_post(get, self.test_system)
self.assertTrue(result['success'])
# 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)
@@ -216,7 +219,7 @@ class OpenEndedModuleTest(unittest.TestCase):
def test_send_to_grader(self):
submission = "This is a student submission"
qtime = datetime.strftime(datetime.now(), xqueue_interface.dateformat)
- student_info = {'anonymous_student_id': test_system.anonymous_student_id,
+ student_info = {'anonymous_student_id': self.test_system.anonymous_student_id,
'submission_time': qtime}
contents = self.openendedmodule.payload.copy()
contents.update({
@@ -224,7 +227,7 @@ class OpenEndedModuleTest(unittest.TestCase):
'student_response': submission,
'max_score': self.max_score
})
- result = self.openendedmodule.send_to_grader(submission, test_system)
+ result = self.openendedmodule.send_to_grader(submission, self.test_system)
self.assertTrue(result)
self.mock_xqueue.send_to_queue.assert_called_with(body=json.dumps(contents), header=ANY)
@@ -238,7 +241,7 @@ class OpenEndedModuleTest(unittest.TestCase):
}
get = {'queuekey': "abcd",
'xqueue_body': score_msg}
- self.openendedmodule.update_score(get, test_system)
+ self.openendedmodule.update_score(get, self.test_system)
def update_score_single(self):
self.openendedmodule.new_history_entry("New Entry")
@@ -261,11 +264,11 @@ class OpenEndedModuleTest(unittest.TestCase):
}
get = {'queuekey': "abcd",
'xqueue_body': json.dumps(score_msg)}
- self.openendedmodule.update_score(get, test_system)
+ self.openendedmodule.update_score(get, self.test_system)
def test_latest_post_assessment(self):
self.update_score_single()
- assessment = self.openendedmodule.latest_post_assessment(test_system)
+ assessment = self.openendedmodule.latest_post_assessment(self.test_system)
self.assertFalse(assessment == '')
# check for errors
self.assertFalse('errors' in assessment)
@@ -336,7 +339,13 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
descriptor = Mock()
def setUp(self):
- self.combinedoe = CombinedOpenEndedV1Module(test_system, self.location, self.definition, self.descriptor, static_data = self.static_data, metadata=self.metadata)
+ self.test_system = test_system()
+ self.combinedoe = CombinedOpenEndedV1Module(self.test_system,
+ self.location,
+ self.definition,
+ self.descriptor,
+ static_data = self.static_data,
+ metadata=self.metadata)
def test_get_tag_name(self):
name = self.combinedoe.get_tag_name("
Tag")
diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py
index 361a6ea785..16bd222b9e 100644
--- a/common/lib/xmodule/xmodule/tests/test_conditional.py
+++ b/common/lib/xmodule/xmodule/tests/test_conditional.py
@@ -56,6 +56,9 @@ class ConditionalModuleTest(unittest.TestCase):
'''Get a dummy system'''
return DummySystem(load_error_modules)
+ def setUp(self):
+ self.test_system = test_system()
+
def get_course(self, name):
"""Get a test course by directory name. If there's more than one, error."""
print "Importing {0}".format(name)
@@ -85,14 +88,14 @@ class ConditionalModuleTest(unittest.TestCase):
location = descriptor.location
instance_state = instance_states.get(location.category, None)
print "inner_get_module, location=%s, inst_state=%s" % (location, instance_state)
- return descriptor.xmodule_constructor(test_system)(instance_state, shared_state)
+ return descriptor.xmodule_constructor(self.test_system)(instance_state, shared_state)
location = Location(["i4x", "edX", "cond_test", "conditional", "condone"])
def replace_urls(text, staticfiles_prefix=None, replace_prefix='/static/', course_namespace=None):
return text
- test_system.replace_urls = replace_urls
- test_system.get_module = inner_get_module
+ self.test_system.replace_urls = replace_urls
+ self.test_system.get_module = inner_get_module
module = inner_get_module(location)
print "module: ", module
diff --git a/common/lib/xmodule/xmodule/tests/test_self_assessment.py b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
index b9c3076b7c..362b73df67 100644
--- a/common/lib/xmodule/xmodule/tests/test_self_assessment.py
+++ b/common/lib/xmodule/xmodule/tests/test_self_assessment.py
@@ -53,13 +53,13 @@ class SelfAssessmentTest(unittest.TestCase):
'skip_basic_checks' : False,
}
- self.module = SelfAssessmentModule(test_system, self.location,
+ self.module = SelfAssessmentModule(test_system(), self.location,
self.definition, self.descriptor,
static_data,
state, metadata=self.metadata)
def test_get_html(self):
- html = self.module.get_html(test_system)
+ html = self.module.get_html(self.module.system)
self.assertTrue("This is sample prompt text" in html)
def test_self_assessment_flow(self):
@@ -82,10 +82,11 @@ class SelfAssessmentTest(unittest.TestCase):
self.assertEqual(self.module.get_score()['score'], 0)
- self.module.save_answer({'student_answer': "I am an answer"}, test_system)
+ self.module.save_answer({'student_answer': "I am an answer"},
+ self.module.system)
self.assertEqual(self.module.state, self.module.ASSESSING)
- self.module.save_assessment(mock_query_dict, test_system)
+ self.module.save_assessment(mock_query_dict, self.module.system)
self.assertEqual(self.module.state, self.module.DONE)
@@ -94,7 +95,8 @@ class SelfAssessmentTest(unittest.TestCase):
self.assertEqual(self.module.state, self.module.INITIAL)
# if we now assess as right, skip the REQUEST_HINT state
- self.module.save_answer({'student_answer': 'answer 4'}, test_system)
+ self.module.save_answer({'student_answer': 'answer 4'},
+ self.module.system)
responses['assessment'] = '1'
- self.module.save_assessment(mock_query_dict, test_system)
+ self.module.save_assessment(mock_query_dict, self.module.system)
self.assertEqual(self.module.state, self.module.DONE)