Commit d5de1d82 by Chris Dodge

store declines in the CreditRequirementStatus table

parent 4f897783
......@@ -600,11 +600,13 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
exam = get_exam_by_id(exam_id)
if to_status == ProctoredExamStudentAttemptStatus.verified:
verification = 'satisfied'
credit_requirement_status = 'satisfied'
elif to_status == ProctoredExamStudentAttemptStatus.submitted:
verification = 'submitted'
credit_requirement_status = 'submitted'
elif to_status == ProctoredExamStudentAttemptStatus.declined:
credit_requirement_status = 'declined'
else:
verification = 'failed'
credit_requirement_status = 'failed'
log_msg = (
'Calling set_credit_requirement_status for '
......@@ -613,7 +615,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
user_id=exam_attempt_obj.user_id,
course_id=exam['course_id'],
content_id=exam_attempt_obj.proctored_exam.content_id,
status=verification
status=credit_requirement_status
)
)
log.info(log_msg)
......@@ -623,7 +625,7 @@ def update_attempt_status(exam_id, user_id, to_status, raise_if_not_found=True,
course_key_or_id=exam['course_id'],
req_namespace='proctored_exam',
req_name=exam_attempt_obj.proctored_exam.content_id,
status=verification
status=credit_requirement_status
)
if cascade_effects and ProctoredExamStudentAttemptStatus.is_a_cascadable_failure(to_status):
......@@ -963,6 +965,7 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
satisfied_prerequisites = []
failed_prerequisites = []
pending_prerequisites = []
declined_prerequisites = []
# insure an ordered and filtered list
# we remove 'grade' requirements since those cannot be
......@@ -996,6 +999,8 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
satisfied_prerequisites.append(requirement)
elif status == 'failed':
failed_prerequisites.append(requirement)
elif status == 'declined':
declined_prerequisites.append(requirement)
else:
pending_prerequisites.append(requirement)
......@@ -1005,12 +1010,13 @@ def _are_prerequirements_satisfied(prerequisites_statuses, evaluate_for_requirem
# all prequisites are satisfied if there are no failed or pending requirement
# statuses
'are_prerequisites_satisifed': (
not failed_prerequisites and not pending_prerequisites
not failed_prerequisites and not pending_prerequisites and not declined_prerequisites
),
# note that we reverse the list here, because we assempled it by walking backwards
'satisfied_prerequisites': list(reversed(satisfied_prerequisites)),
'failed_prerequisites': list(reversed(failed_prerequisites)),
'pending_prerequisites': list(reversed(pending_prerequisites)),
'declined_prerequisites': list(reversed(declined_prerequisites))
}
......@@ -1364,7 +1370,9 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
# so, show them:
# 1) If there are failed prerequisites then block user and say why
# 2) If there are pending prerequisites then block user and allow them to remediate them
# 3) Otherwise - all prerequisites are satisfied - then give user
# 3) If there are declined prerequisites, then we auto-decline proctoring since user
# explicitly declined their interest in credit
# 4) Otherwise - all prerequisites are satisfied - then give user
# option to take exam as proctored
# get information about prerequisites
......@@ -1386,7 +1394,23 @@ def _get_proctored_exam_view(exam, context, exam_id, user_id, course_id):
})
if not prerequisite_status['are_prerequisites_satisifed']:
# do we have failed prerequisites? That takes priority
# do we have any declined prerequisites, if so, then we
# will auto-decline this proctored exam
if prerequisite_status['declined_prerequisites']:
# user hasn't a record of attempt, create one now
# so we can mark it as declined
create_exam_attempt(exam_id, user_id)
update_attempt_status(
exam_id,
user_id,
ProctoredExamStudentAttemptStatus.declined,
raise_if_not_found=False
)
return None
# do we have failed prerequisites? That takes priority in terms of
# messaging
if prerequisite_status['failed_prerequisites']:
# Let's resolve the URLs to jump to this prequisite
prerequisite_status['failed_prerequisites'] = _resolve_prerequisite_links(
......
......@@ -155,6 +155,39 @@ class ProctoredExamApiTests(LoggedInTestCase):
},
]
self.declined_prerequisites = [
{
'namespace': 'proctoring',
'name': 'proc1',
'order': 2,
'status': 'satisfied',
},
{
'namespace': 'reverification',
'name': 'rever1',
'order': 1,
'status': 'satisfied',
},
{
'namespace': 'grade',
'name': 'grade1',
'order': 0,
'status': 'pending',
},
{
'namespace': 'reverification',
'name': 'rever2',
'order': 3,
'status': 'declined',
},
{
'namespace': 'proctoring',
'name': 'proc2',
'order': 4,
'status': 'pending',
},
]
def _create_proctored_exam(self):
"""
Calls the api's create_exam to create an exam object.
......@@ -575,6 +608,7 @@ class ProctoredExamApiTests(LoggedInTestCase):
@ddt.data(
(ProctoredExamStudentAttemptStatus.verified, 'satisfied'),
(ProctoredExamStudentAttemptStatus.submitted, 'submitted'),
(ProctoredExamStudentAttemptStatus.declined, 'declined'),
(ProctoredExamStudentAttemptStatus.error, 'failed')
)
@ddt.unpack
......@@ -797,11 +831,15 @@ class ProctoredExamApiTests(LoggedInTestCase):
('reverification', 'pending', 'The following prerequisites are in a <strong>pending</strong> state', True),
('reverification', 'failed', 'You did not satisfy the following prerequisites', True),
('reverification', 'satisfied', 'To be eligible to earn credit for this course', False),
('reverification', 'declined', None, False),
('proctored_exam', None, 'The following prerequisites are in a <strong>pending</strong> state', True),
('proctored_exam', 'pending', 'The following prerequisites are in a <strong>pending</strong> state', True),
('proctored_exam', 'failed', 'You did not satisfy the following prerequisites', True),
('proctored_exam', 'satisfied', 'To be eligible to earn credit for this course', False),
('grade', 'failed', 'To be eligible to earn credit for this course', False)
('proctored_exam', 'declined', None, False),
('grade', 'failed', 'To be eligible to earn credit for this course', False),
# this is nonesense, but let's double check it
('grade', 'declined', 'To be eligible to earn credit for this course', False),
)
@ddt.unpack
def test_prereq_scenarios(self, namespace, req_status, expected_content, should_see_prereq):
......@@ -839,7 +877,16 @@ class ProctoredExamApiTests(LoggedInTestCase):
}
)
self.assertIn(expected_content, rendered_response)
if expected_content:
self.assertIn(expected_content, rendered_response)
else:
self.assertIsNone(rendered_response)
if req_status == 'declined' and not expected_content:
# also we should have auto-declined if a pre-requisite was declined
attempt = get_exam_attempt(exam['id'], self.user_id)
self.assertIsNotNone(attempt)
self.assertEqual(attempt['status'], ProctoredExamStudentAttemptStatus.declined)
if should_see_prereq:
self.assertIn('Foo Requirement', rendered_response)
......@@ -2134,19 +2181,20 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.assertEqual(ordered_list[3]['name'], 'proc2')
@ddt.data(
('rever1', True, 0, 0, 0),
('proc1', True, 1, 0, 0),
('rever2', True, 2, 0, 0),
('proc2', False, 2, 1, 0),
('unknown', False, 2, 1, 1),
(None, False, 2, 1, 1),
('rever1', True, 0, 0, 0, 0),
('proc1', True, 1, 0, 0, 0),
('rever2', True, 2, 0, 0, 0),
('proc2', False, 2, 1, 0, 0),
('unknown', False, 2, 1, 1, 0),
(None, False, 2, 1, 1, 0),
)
@ddt.unpack
def test_are_prerequisite_satisifed(self, content_id,
expected_are_prerequisites_satisifed,
expected_len_satisfied_prerequisites,
expected_len_failed_prerequisites,
expected_len_pending_prerequisites):
expected_len_pending_prerequisites,
expected_len_declined_prerequisites):
"""
verify proper operation of the logic when computing is prerequisites are satisfied
"""
......@@ -2161,3 +2209,35 @@ class ProctoredExamApiTests(LoggedInTestCase):
self.assertEqual(len(results['satisfied_prerequisites']), expected_len_satisfied_prerequisites)
self.assertEqual(len(results['failed_prerequisites']), expected_len_failed_prerequisites)
self.assertEqual(len(results['pending_prerequisites']), expected_len_pending_prerequisites)
self.assertEqual(len(results['declined_prerequisites']), expected_len_declined_prerequisites)
@ddt.data(
('rever1', True, 0, 0, 0, 0),
('proc1', True, 1, 0, 0, 0),
('rever2', True, 2, 0, 0, 0),
('proc2', False, 2, 0, 0, 1),
('unknown', False, 2, 0, 1, 1),
(None, False, 2, 0, 1, 1),
)
@ddt.unpack
def test_declined_prerequisites(self, content_id,
expected_are_prerequisites_satisifed,
expected_len_satisfied_prerequisites,
expected_len_failed_prerequisites,
expected_len_pending_prerequisites,
expected_len_declined_prerequisites):
"""
verify proper operation of the logic when computing is prerequisites are satisfied
"""
results = _are_prerequirements_satisfied(
self.declined_prerequisites,
content_id,
filter_out_namespaces=['grade']
)
self.assertEqual(results['are_prerequisites_satisifed'], expected_are_prerequisites_satisifed)
self.assertEqual(len(results['satisfied_prerequisites']), expected_len_satisfied_prerequisites)
self.assertEqual(len(results['failed_prerequisites']), expected_len_failed_prerequisites)
self.assertEqual(len(results['pending_prerequisites']), expected_len_pending_prerequisites)
self.assertEqual(len(results['declined_prerequisites']), expected_len_declined_prerequisites)
......@@ -1573,7 +1573,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
)
self.assertEqual(response.status_code, 200)
# make sure we failed the requirement status
# make sure we declined the requirement status
credit_service = get_runtime_service('credit')
credit_status = credit_service.get_credit_state(self.user.id, proctored_exam.course_id)
......@@ -1581,7 +1581,7 @@ class TestStudentProctoredExamAttempt(LoggedInTestCase):
self.assertEqual(len(credit_status['credit_requirement_status']), 1)
self.assertEqual(
credit_status['credit_requirement_status'][0]['status'],
'failed'
'declined'
)
def test_exam_callback(self):
......
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