Commit 6a1a6b7a by chrisndodge

Merge pull request #63 from edx/cdodge/add-nav-states

Cdodge/add nav states
parents 3e6edabd b1282624
...@@ -7,7 +7,11 @@ API which is in the views.py file, per edX coding standards ...@@ -7,7 +7,11 @@ API which is in the views.py file, per edX coding standards
""" """
import pytz import pytz
import uuid import uuid
import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from django.utils.translation import ugettext as _
from django.conf import settings from django.conf import settings
from django.template import Context, loader from django.template import Context, loader
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
...@@ -36,6 +40,8 @@ from edx_proctoring.utils import humanized_time ...@@ -36,6 +40,8 @@ from edx_proctoring.utils import humanized_time
from edx_proctoring.backends import get_backend_provider from edx_proctoring.backends import get_backend_provider
from edx_proctoring.runtime import get_runtime_service from edx_proctoring.runtime import get_runtime_service
log = logging.getLogger(__name__)
def is_feature_enabled(): def is_feature_enabled():
""" """
...@@ -646,6 +652,89 @@ def _check_credit_eligibility(credit_state): ...@@ -646,6 +652,89 @@ def _check_credit_eligibility(credit_state):
return True return True
STATUS_SUMMARY_MAP = {
'_default': {
'short_description': _('Taking As Proctored Exam'),
'suggested_icon': 'fa-lock',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.eligible: {
'short_description': _('Proctored Option Available'),
'suggested_icon': 'fa-lock',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.declined: {
'short_description': _('Taking As Open Exam'),
'suggested_icon': 'fa-unlock',
'in_completed_state': False
},
ProctoredExamStudentAttemptStatus.submitted: {
'short_description': _('Pending Session Review'),
'suggested_icon': 'fa-spinner fa-spin',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.verified: {
'short_description': _('Passed Proctoring'),
'suggested_icon': 'fa-check',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.rejected: {
'short_description': _('Failed Proctoring'),
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
},
ProctoredExamStudentAttemptStatus.error: {
'short_description': _('Failed Proctoring'),
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
}
}
def get_attempt_status_summary(user_id, course_id, content_id):
"""
Returns a summary about the status of the attempt for the user
in the course_id and content_id
Return will be:
None: Not applicable
- or -
{
'status': ['eligible', 'declined', 'submitted', 'verified', 'rejected'],
'short_description': <short description of status>,
'suggested_icon': <recommended font-awesome icon to use>,
'in_completed_state': <if the status is considered in a 'completed' state>
}
"""
# as a quick exit, let's check credit eligibility
credit_service = get_runtime_service('credit')
if credit_service:
credit_state = credit_service.get_credit_state(user_id, unicode(course_id))
if not _check_credit_eligibility(credit_state):
return None
try:
exam = get_exam_by_content_id(course_id, content_id)
except ProctoredExamNotFoundException, ex:
# this really shouldn't happen, but log it at least
log.exception(ex)
return None
attempt = get_exam_attempt(exam['id'], user_id)
status = attempt['status'] if attempt else ProctoredExamStudentAttemptStatus.eligible
summary = None
if status in STATUS_SUMMARY_MAP:
summary = STATUS_SUMMARY_MAP[status]
else:
summary = STATUS_SUMMARY_MAP['_default']
summary.update({"status": status})
return summary
def get_student_view(user_id, course_id, content_id, def get_student_view(user_id, course_id, content_id,
context, user_role='student'): context, user_role='student'):
""" """
......
...@@ -33,6 +33,7 @@ from edx_proctoring.api import ( ...@@ -33,6 +33,7 @@ from edx_proctoring.api import (
mark_exam_attempt_timeout, mark_exam_attempt_timeout,
mark_exam_attempt_as_ready, mark_exam_attempt_as_ready,
update_attempt_status, update_attempt_status,
get_attempt_status_summary,
) )
from edx_proctoring.exceptions import ( from edx_proctoring.exceptions import (
ProctoredExamAlreadyExists, ProctoredExamAlreadyExists,
...@@ -1160,3 +1161,140 @@ class ProctoredExamApiTests(LoggedInTestCase): ...@@ -1160,3 +1161,140 @@ class ProctoredExamApiTests(LoggedInTestCase):
exam_attempt['status'], exam_attempt['status'],
ProctoredExamStudentAttemptStatus.ready_to_submit ProctoredExamStudentAttemptStatus.ready_to_submit
) )
@ddt.data(
(
ProctoredExamStudentAttemptStatus.eligible, {
'status': ProctoredExamStudentAttemptStatus.eligible,
'short_description': 'Proctored Option Available',
'suggested_icon': 'fa-lock',
'in_completed_state': False
}
),
(
ProctoredExamStudentAttemptStatus.declined, {
'status': ProctoredExamStudentAttemptStatus.declined,
'short_description': 'Taking As Open Exam',
'suggested_icon': 'fa-unlock',
'in_completed_state': False
}
),
(
ProctoredExamStudentAttemptStatus.submitted, {
'status': ProctoredExamStudentAttemptStatus.submitted,
'short_description': 'Pending Session Review',
'suggested_icon': 'fa-spinner fa-spin',
'in_completed_state': True
}
),
(
ProctoredExamStudentAttemptStatus.verified, {
'status': ProctoredExamStudentAttemptStatus.verified,
'short_description': 'Passed Proctoring',
'suggested_icon': 'fa-check',
'in_completed_state': True
}
),
(
ProctoredExamStudentAttemptStatus.rejected, {
'status': ProctoredExamStudentAttemptStatus.rejected,
'short_description': 'Failed Proctoring',
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
}
),
(
ProctoredExamStudentAttemptStatus.error, {
'status': ProctoredExamStudentAttemptStatus.error,
'short_description': 'Failed Proctoring',
'suggested_icon': 'fa-exclamation-triangle',
'in_completed_state': True
}
),
(
ProctoredExamStudentAttemptStatus.created, {
'status': ProctoredExamStudentAttemptStatus.created,
'short_description': 'Taking As Proctored Exam',
'suggested_icon': 'fa-lock',
'in_completed_state': False
}
),
(
ProctoredExamStudentAttemptStatus.ready_to_start, {
'status': ProctoredExamStudentAttemptStatus.ready_to_start,
'short_description': 'Taking As Proctored Exam',
'suggested_icon': 'fa-lock',
'in_completed_state': False
}
),
(
ProctoredExamStudentAttemptStatus.started, {
'status': ProctoredExamStudentAttemptStatus.started,
'short_description': 'Taking As Proctored Exam',
'suggested_icon': 'fa-lock',
'in_completed_state': False
}
),
(
ProctoredExamStudentAttemptStatus.ready_to_submit, {
'status': ProctoredExamStudentAttemptStatus.ready_to_submit,
'short_description': 'Taking As Proctored Exam',
'suggested_icon': 'fa-lock',
'in_completed_state': False
}
)
)
@ddt.unpack
def test_attempt_status_summary(self, status, expected):
"""
Assert that we get the expected status summaries
"""
exam_attempt = self._create_started_exam_attempt()
update_attempt_status(
exam_attempt.proctored_exam_id,
self.user.id,
status
)
summary = get_attempt_status_summary(
self.user.id,
exam_attempt.proctored_exam.course_id,
exam_attempt.proctored_exam.content_id
)
self.assertIn(summary, [expected])
@ddt.data(
'honor', 'staff'
)
def test_status_summary_honor(self, enrollment_mode):
"""
Make sure status summary is None for a non-verified person
"""
set_runtime_service('credit', MockCreditService(enrollment_mode=enrollment_mode))
exam_attempt = self._create_started_exam_attempt()
summary = get_attempt_status_summary(
self.user.id,
exam_attempt.proctored_exam.course_id,
exam_attempt.proctored_exam.content_id
)
self.assertIsNone(summary)
def test_status_summary_bad(self):
"""
Make sure we get back a None when getting summary for content that does not
exist
"""
summary = get_attempt_status_summary(
self.user.id,
'foo',
'foo'
)
self.assertIsNone(summary)
...@@ -17,12 +17,12 @@ class MockCreditService(object): ...@@ -17,12 +17,12 @@ class MockCreditService(object):
Simple mock of the Credit Service Simple mock of the Credit Service
""" """
def __init__(self): def __init__(self, enrollment_mode='verified'):
""" """
Initializer Initializer
""" """
self.status = { self.status = {
'enrollment_mode': 'verified', 'enrollment_mode': enrollment_mode,
'profile_fullname': 'Wolfgang von Strucker', 'profile_fullname': 'Wolfgang von Strucker',
'credit_requirement_status': [] 'credit_requirement_status': []
} }
......
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